我对java.nio.Buffer
有些疑问。基本上,我的问题始于是否始终需要flip()
调用以在读取和写入之间切换,或者仅在慢速I / O时需要{1}}调用,例如,在写入然后读取的情况下,确保数据在读取之前完全写入。我的特殊问题是使用mappedByteBuffer。看起来如果文件存在且大小与我所知,我可以使用position(int newPosition)
调用导航到文件的任何部分,并执行读或写,即基本上将缓冲区用作块内存忘记了标记或限制的概念。这是真的吗?
考虑以下示例。如果我有一个包含整数1的文件,那么从头开始是2,似乎我可以在位置0放置另一个整数3,倒带并从缓冲区读取3和2。不应该像普通的非mmap缓冲区那样限制阻止我从第二个getInt?我什么时候需要调用flip()来在mappedByteBuffer的写入和读取之间切换?谢谢!
final int FILESIZE = 1024;
RandomAccessFile fileHandle;
FileChannel fileChannel;
File testFile = new File("c:/temp/testbbrw.dat");
fileHandle = new RandomAccessFile(testFile, "rw");
fileChannel = fileHandle.getChannel();
MappedByteBuffer mbb = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, FILESIZE);
int pos, data;
mbb.position(0);
mbb.putInt(3);
mbb.position(0);
data=mbb.getInt(); //I get 3
data=mbb.getInt(); //I get 2, which was written to the file before this program runs
mbb.force();
fileHandle.close();
答案 0 :(得分:2)
这就是Buffer.flip的作用
347 public final Buffer flip() {
348 limit = position;
349 position = 0;
350 mark = -1;
351 return this;
352 }
正在准备缓冲区,以便缓冲区上的下一个读操作从位置0开始并以当前限制结束。意思是你告诉它,你完成了更改缓冲区并准备好移动或复制到其他地方(这意味着阅读它)
答案 1 :(得分:0)
我的问题始于是否总是需要在读取和写入之间切换的flip()调用,或者只需要缓慢的I / O,例如在写然后读取的情况下,以确保数据在读取之前完全写入。
Buffer
都是在您可以阅读或加入它的状态下开始的,这是相同的。flip()
将其置于一个可以从中写入或从中获取的状态,这是相同的事情。flip()
不是flip()
的倒数。其唯一的反转是compact()
和clear()
。为了清楚起见,我发现最好始终将Buffer
置于可读状态,并且只在需要时将其翻转为可写状态,然后立即将其恢复为可读状态。
这适用于I / O.
如果你只做get()
和put()
,我不确定我会使用flip()
,因为这是MappedByteBuffer
我肯定不会请致电clear()
或compact()
,这两者都可能对文件造成严重后果,并且还会使用flip()
排除。
答案 2 :(得分:0)
与典型的循环有限缓冲区相比,Java中Buffer
API的设计令人困惑且反直觉。更糟糕的是文档中的术语选择不当,加上读/写和put / get术语的模棱两可使用,前者指的是外部操作(通常由Channel
) 使用 一个Buffer
,后一个操作 由 提供Buffer
。
在创建时,新缓冲区为“空”,准备填充。它可能会立即在构造函数中提供一些内容,但它仍然处于“填充”状态。
flip()
方法“翻转”缓冲区的逻辑状态从填充到清空。相反,愚蠢flip()
并没有自我反转,即使在普通英语中它通常会描述逻辑上可逆的动作。实际上,查看代码,在没有插入clear
或compact
的情况下调用它两次会将缓冲区设置为无效状态,从而导致其他方法返回无意义。 [1]
clear()
和compact()
方法是flip()
的逻辑反转,将缓冲区恢复为“填充”状态,前者也将其清空,后者保留剩余内容
一般建议是使用try / finally将任何给定缓冲区保持在一致状态始终;例如:
ByteBuffer wrap(ByteBuffer src, ByteBuffer tgt) {
// assume buffers are *always* kept in the "filling" state
try {
src.flip(); // change `src` to "emptying"; assume tgt already filling
// transfer some or all of `src` to `tgt`
}
finally {
if(src.remaining()) { src.compact(); } // revert `src` to "filling" without discarding remaining data
else { src.clear(); } // compact() is (usually) less efficient than clearing
}
Java缓冲区最直观的原因是大多数循环缓冲区实现同时具有读/写功能,因此它们保持三个值head
,tail
和{{ 1}}(如果语言允许,最后经常从后备数组中推断出来) 和 它们只允许值换行。 capacity
是读取数据的地方,head
是写入数据的位置。当到达底层数组的末尾时,head / tail的值只是设置为零(即它循环)。
当tail
时,缓冲区为空。当head == tail
时,缓冲区已满,当前内容长度由inc(tail) == head
到达。后备数组的大小通常为head <= tail ? (tail - head) : (capacity - head + tail)
,这样当缓冲区已满时capacity+1
索引不等于tail
(如果没有单独的标志,这将是不明确的)。
这使得内部索引处理稍微复杂一些,但是为了不必触发状态并且永远不需要将数据“压缩”回内部数组的开头(尽管大多数实现将重置启动/每当缓冲区清空时,将索引结束为零。
通常情况下,这也意味着在读取时可能需要两个阵列副本;首先从head
到数组的末尾,然后从数组的开头到head
。当目标也是缓冲区并在写入期间换行时可能需要三个复制操作(但额外的副本隐藏在tail
方法中)。
我最好的猜测是Java以这种方式定义缓冲区,以便对缓冲区的所有读写都发生在连续的块中。据推测,这可以在处理套接字,内存映射和通道等操作时实现下游/内部优化,避免需要进行中间复制。
在这里猜测。
[1]无效,因为双重翻转会导致限制设置为0,而不是put
,这会导致capacity
由于BufferOverflowException
而导致大多数内部方法出现limit - position
; = 0或位置&gt; =限制。例如:
511 final int nextPutIndex() { // package-private
512 if (position >= limit)
513 throw new BufferOverflowException();
514 return position++;
515 }
516
517 final int nextPutIndex(int nb) { // package-private
518 if (limit - position < nb)
519 throw new BufferOverflowException();
520 int p = position;
521 position += nb;
522 return p;
523 }
524
525 /**
526 * Checks the given index against the limit, throwing an {@link
527 * IndexOutOfBoundsException} if it is not smaller than the limit
528 * or is smaller than zero.
529 */
530 final int checkIndex(int i) { // package-private
531 if ((i < 0) || (i >= limit))
532 throw new IndexOutOfBoundsException();
533 return i;
534 }