Java mappedByteBuffer和flip()和position()

时间:2013-02-08 06:32:00

标签: java buffer nio

我对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();

3 个答案:

答案 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,例如在写然后读取的情况下,以确保数据在读取之前完全写入。

  1. 任何描述中的Buffer都是在您可以阅读或加入它的状态下开始的,这是相同的。
  2. flip()将其置于一个可以从中写入或从中获取的状态,这是相同的事情。
  3. 尽管它的名字非常愚蠢,flip()不是flip()的倒数。其唯一的反转是compact()clear()
  4. 为了清楚起见,我发现最好始终将Buffer置于可读状态,并且只在需要时将其翻转为可写状态,然后立即将其恢复为可读状态。

    这适用于I / O.

    如果你只做get()put(),我不确定我会使用flip(),因为这是MappedByteBuffer我肯定不会请致电clear()compact(),这两者都可能对文件造成严重后果,并且还会使用flip()排除。

答案 2 :(得分:0)

与典型的循环有限缓冲区相比,Java中Buffer API的设计令人困惑且反直觉。更糟糕的是文档中的术语选择不当,加上读/写和put / get术语的模棱两可使用,前者指的是外部操作(通常由Channel 使用 一个Buffer,后一个操作 提供Buffer

Java缓冲区

在创建时,新缓冲区为“空”,准备填充。它可能会立即在构造函数中提供一些内容,但它仍然处于“填充”状态。

flip()方法“翻转”缓冲区的逻辑状态从填充到清空。相反,愚蠢flip()并没有自我反转,即使在普通英语中它通常会描述逻辑上可逆的动作。实际上,查看代码,在没有插入clearcompact的情况下调用它两次会将缓冲区设置为无效状态,从而导致其他方法返回无意义。 [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缓冲区最直观的原因是大多数循环缓冲区实现同时具有读/写功能,因此它们保持三个值headtail和{{ 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       }