与典型的循环有限缓冲区相比,Java中的 API 设计Buffer
令人困惑且违反直觉。更糟糕的是文档中的术语选择不当,加上读/写和 put/get 术语的模棱两可使用,前者指的是使用a的外部操作(通常由 a Channel
),而后者指的是由a提供的操作。Buffer
Buffer
Java 缓冲区
在创建时,一个新的缓冲区是“空的”,准备好被填充。它可能会立即在构造函数中提供一些内容,但它仍处于“填充”状态。
该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
(capacity
如果语言允许,最后一个通常从后备数组推断出来)他们只是允许值换行。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 }