ByteBuffer.flip()和ByteBuffer.rewind()的区别

32

我知道flip()会将当前的缓冲区位置设置为0,并将限制设置为先前的缓冲区位置,而rewind()只是将当前的缓冲区位置设置为0。

在下面的代码中,无论我使用rewind()还是flip(),都会得到相同的结果。

byte b = 127;
bb.put(b);
bb.rewind();//or flip();

System.out.println(bb.get());
bb.rewind();// or flip();
System.out.println(bb.get());

能否给我提供一个真实的例子,证明使用这两种方法的差异确实很重要?先谢谢了。

7个回答

21

从源代码来看,它们非常相似。您可以看到以下内容:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

因此,区别在于fliplimit设置为position,而rewind不会。 假设您已经分配了一个8字节的缓冲区,您已经使用4字节填充了缓冲区,然后将位置设置为3,如下所示:

    [ 1  1  1  1  0  0  0  0]
               |           |
flip           limit       |
rewind                     limit

所以,rewind 只是使用了适当设置的限制。


18

它们根本不相等。

ByteBuffer 通常可用于 read()(或 put())。

flip() 使其可用于 write()(或 get())。

rewind()compact()clear() 在经过 write()(或 get())之后,将其置于再次可用于 read()/put() 的状态。


1
请问您为什么写了read()/put()write()/get()?难道不应该是read()/get()write()/put()吗?因为获取操作意味着读取,而放置则意味着写入。 - user963241
@user207421 是的,它确实有用。一开始可能会感到困惑,因为OP的问题与Channels无关,而我的用例也与Channels无关。但我理解人们最常用Channels进行的操作是从通道中读取和写入缓冲区。 - haelix
@haelix,你一再声称的内容是完全不正确的。这已经在黑白之间明确表述了。答案解释了read()write()或者get()put()操作的上下文,以涵盖所有可能性,这正是一个好答案应该做的,而后者与Channels毫无关系。你正在违背现实。 - user207421
1
@user207421 好的,我显然可以看到答案包含了什么。我试图表达的观点是,答案强调了Channelreadwrite方法,只是因为它们首先被提到,并且getput方法在括号里面。我认为一个伟大的答案(我说过你的不是)应该强调ByteBuffer本身的方法。但是这个答案还是可以的,只是不是“伟大”的,而且这只是我的观点,与“现实”没有太多关系。 - haelix
@haekix 尽管有这么多的纠结,但事实仍然是答案包含了你所声称缺失的所有上下文。 - user207421
显示剩余3条评论

4

rewind( ) 方法与 flip( ) 方法类似,但不影响限制。它只将位置设置回 0。您可以使用 rewind() 方法返回并重新读取已经翻转的缓冲区中的数据。一个常见的情况是:在使用flip()方法读取缓冲区中的数据后,您想要重新读取数据,则该方法适用。


1
这里有一个例子,两者将产生不同的结果。正如你所说,两者都将位置设置为0,两者之间的区别在于flip将限制设置为先前的位置。
因此,使用flip,如果你正在写入(put),读取(get)的限制将变为你写入的最后一个元素的位置。如果你尝试读取更多的内容,它将抛出异常。
Rewind不改变限制。假设它已经到达容量(缓冲区大小),它将让你继续读取实际上没有写入的数据,例如读取缓冲区初始化时的初始零值。
ByteBuffer bb = ByteBuffer.allocateDirect(2);
byte b = 127;
bb.put(b);
bb.rewind();//or flip();

System.out.println(bb.get());  // will print 127 in either case
System.out.println(bb.get());  // will print 0 for rewind, BufferUnderflow exception for flip

1

Buffer具有位置,限制和容量属性。您可以在创建缓冲区时为n个元素分配空间。这里n是容量。缓冲区分配后,将位置设置为0,并将限制设置为容量。

如果您使用n-x个元素填充了缓冲区,则位置将设置为n-x。 n-x之后,缓冲区将有空元素。

如果此时您想排干缓冲区,而且只想要非空值,则需要将限制设置为当前位置,将位置设置为零。使用hasRemaining(),可以获取到n-x个元素。Flip会像上面描述的那样设置限制和位置属性。

flip和rewind之间的区别在于flip将位置设置为0,并将限制设置为活动内容。方法rewind仅将位置设置为0。

更多信息请参见http://www.zoftino.com/java-nio-tutorial


1
缓冲区在概念上是一个顺序存储数据的地方(假设是字节),你可以向其中写入数据,并且可以从中读取数据。 (忘掉与“通道”的“读取”和“写入”的任何关联。当我说“读取”时,我指的是Buffer.get,当我说“写入”时,我指的是Buffer.put)
常见的用例是向缓冲区写入内容,然后读取所写入的内容。
“限制”标记了缓冲区中读取或写入操作应该停止的位置:它是第一个不应该被读取/写入的位置。
如果我们想向缓冲区写入内容,我们希望从开头开始,并在缓冲区满时停止;提前停止没有意义(除非我们没有数据可用)。因此,将“限制”设置为缓冲区的“容量”是有意义的。 因此,当我们想要开始向缓冲区写入内容时,我们调用“clear”。这将将当前“位置”设置为缓冲区的开头,并将“限制”设置为其容量。
如果我们想要从缓冲区开始读取,情况就不同了。我们不知道实际写入缓冲区的元素数量 - 我们不知道它是否已满。但通常我们只想读取与写入的元素数量相同的元素:超过这个点的任何内容都是垃圾;读取到缓冲区末尾可能是一个错误!
幸运的是,之前的写操作已经将最后的位置保留在原地,所以缓冲区本身知道写入了多少元素。它通过 flip 方法将 limit 设置为当前的 position,然后将当前的 position 设置为 0,很好地封装了这个信息。
实际上,这意味着在调用了 flip 之后,当我们从缓冲区开始读取时,我们将从开头开始,并在读取到上一次写操作写入的最后一个元素后停止。这确保我们不会读取到垃圾数据。 rewind的功能与flip类似,但又有明显的区别。它并不会将limit重置为旧的position。使用rewind通常在你有比仅仅写入和读取缓冲区更具体的用例时才有意义。
例如,如果你已经向缓冲区写入数据,调用了flip,读取了缓冲区,现在想再次读取它(第二次或第三次),那么你会使用rewind
除非你有明确而具体的理由,否则不要使用rewind。对于常见情况,即读取刚刚写入缓冲区的所有数据,请使用flip代替。

将你的示例重写为更接近常见用例的形式:

ByteBuffer bb = ByteBuffer.allocate(2); // limit initially 2
byte b = 127;
bb.put(b);

bb.rewind(); // after this call limit is still 2: probably not what we want!

System.out.println("Reading buffer after rewinding:");
while (bb.hasRemaining()) {
    System.out.println(bb.get()); // two lines! 127 followed by 0
}

ByteBuffer bbFlip = ByteBuffer.allocate(2); // limit initially 2
bbFlip.put(b);

bbFlip.flip(); // after this call limit is 1 because position was 1 when we called flip()

System.out.println("Reading buffer after flipping:");
while (bbFlip.hasRemaining()) {
    System.out.println(bbFlip.get()); // only one line: 127
}

如图所示,当我们以这种方式使用rewind时,我们会丢失有关写入缓冲区的元素数量的信息。如果我们的要求是必须读取刚刚写入的那些(仅限于那些)元素,那么我们刚刚在我们的代码中引入了一个错误!

这确实是更常见的用例之一 - 编辑了答案以包含它。 - undefined

0

@user963241 给 @EJP 的回答增加更多细节。

flip() 使缓冲区准备好进行 write()(或 get())操作。

get() 示例:

你可能想要从缓冲区中读取数据假设你最初将其存储在其中),并将其用于其他用途,例如转换为字符串并对其进行进一步处理。

ByteBuffer buf = ByteBuffer.allocateDirect(80);
private String method(){
buf.flip();
byte[] bytes = byte[10]; //creates a byte array where you can place your data 
buf.get(bytes); //reads data from buffer and places it in the byte array created above
return bytes;
}

write() 示例; 在从套接字通道读取数据到缓冲区之后,您可能希望将其写回套接字通道 - 假设您想要实现类似于从客户端接收到的消息的服务器。

因此,您将从通道读取到缓冲区,然后从缓冲区返回到通道。

SocketChannel socketChannel = SocketChannel.open();
...

ByteBuffer buf = ByteBuffer.allocateDirect(80);

int data = socketChannel.read(buf); // Reads from channel and places it into the buffer
while(data != -1){ //checks if not end of reading
buf.flip();        //prepares for writing
....
socketChannel.write(buf) // if you had initially placed data into buf and you want to read 
                         //from it so that you can write it back into the channel


  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接