1

我有一些问题java.nio.Buffer。基本上,我的问题始于是否flip()总是需要调用来在读取和写入之间切换,或者是否只需要慢速 I/O,例如在先写入再读取的情况下,以确保在读取数据之前完全写入数据. 我的特别问题是 mappedByteBuffer。看起来如果文件存在并且大小我知道,我可以使用position(int newPosition)调用导航到文件的任何部分,并执行读取或写入,即基本上将缓冲区用作一块内存而忘记了标记或限制的概念。这是真的?

考虑以下示例。如果我有一个包含整数 1 的文件,那么从头开始是 2,看来我可以将另一个整数 3 放在位置 0,倒带并从缓冲区中读取 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();
4

3 回答 3

2

这就是 Buffer.flip 所做的

347    public final Buffer flip() {
348        limit = position;
349        position = 0;
350        mark = -1;
351        return this;
352    }

它正在准备缓冲区,以便缓冲区上的下一个读取操作从位置 0 开始并在当前限制处结束。意味着您告诉它,您已完成更改缓冲区并准备将其移动或复制到其他地方(这意味着读取它)

于 2013-02-08T07:09:29.033 回答
0

我的问题始于是否总是需要调用 Flip() 来在读取和写入之间切换,或者是否只需要慢速 I/O,例如在先写入再读取的情况下,以确保数据在写入之前完全写入读。

  1. 任何描述的 ABuffer都以您可以阅读或放入它的状态开始,这是同一件事。
  2. flip()把它置于一个你可以从中写或从中获取的状态,这是同一回事。
  3. 尽管它的(非常愚蠢flip()的)名字,不是flip(). 它唯一的逆元是compact()clear()

为清楚起见,我发现最好始终将 aBuffer置于可读状态,仅在需要时将其翻转为可写状态,然后立即将其恢复为可读状态。

这是用于 I/O。

如果您正在做的只是get()并且put()我不确定我是否会使用flip(),因为这是MappedByteBuffer我当然不会调用clear()or compact(),这两者都可能对文件造成可怕的事情,并且也排除使用flip().

于 2013-02-08T07:29:09.990 回答
0

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

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 缓冲区最反直觉的地方是由于大多数循环缓冲区实现都具有并发读/写能力,因此它们维护三个值 ,headtailcapacity如果语言允许,最后一个通常从后备数组推断出来)他们只是允许值换行。head是读取数据的位置tail,也是写入数据的位置。当到达底层数组的末尾时,head/tail 的值被简单地设置为零(即它循环)。

当 时head == tail,缓冲区为空。当 时inc(tail) == head,缓冲区已满,当前内容长度由 到达head <= tail ? (tail - head) : (capacity - head + tail)。后备数组的大小通常capacity+1使得tail索引不等于head缓冲区已满时的索引(如果没有单独的标志,这将是模棱两可的)。

这使得内部索引处理稍微复杂一些,但是为了不必翻转状态并且永远不需要将数据“压缩”回内部数组的开头(尽管大多数实现会将开始/结束索引重置为清空缓冲区时为零)。

通常,当读取可能需要两个数组副本时,这也转化为权衡;首先从head数组的末尾开始,然后从数组的开头到tail。当目标也是一个缓冲区并在写入期间回绕时,可能需要三个复制操作(但额外的副本隐藏在put方法中)。

假设

我最好的猜测是 Java 以这种方式定义缓冲区,以便对缓冲区的所有读取和写入都发生在连续的块中。这大概可以在处理套接字、内存映射和通道等事物时启用下游/内部优化,从而避免需要制作中间副本。

这里只是猜测。

笔记

[1] 无效,因为双重翻转会导致限制设置为 0,而不是capacity,这反过来会导致BufferOverflowException大多数内部方法由于limit - position<= 0 或位置 >= 限制。例如:

  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       }
于 2017-05-10T20:25:39.447 回答