Javaでの API の設計はBuffer
、典型的な循環型の有限バッファーに比べてわかりにくく、直感に反します。事態をさらに悪化させているのは、ドキュメント内の用語の選択の誤りです。これは、読み取り/書き込みおよびプット/取得という用語のあいまいな使用によって悪化し、前者は を使用した外部操作 (通常は a による) を指し、Channel
後者はBuffer
によって提供される操作を指しBuffer
ます。
Java バッファ
作成時には、新しいバッファは「空」であり、満たす準備ができています。コンストラクターで一部のコンテンツがすぐに提供される場合がありますが、「塗りつぶし」状態のままです。
このflip()
メソッドは、バッファの論理状態を満杯から空に「反転」します。flip()
通常の英語では、通常、論理的に可逆的なアクションを説明しますが、ばかげてそれ自体を逆にすることはありません。実際、コードを見ると、介入せずに 2 回呼び出し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 バッファーが最も直観に反するのは、ほとんどの循環バッファー実装が同時に読み取り/書き込み可能であり、3 つの値を維持するhead
ためtail
ですcapacity
。そして、それらは単に値が折り返されることを許可します。はデータのhead
読み取り元であり、 は書き込み先tail
です。下層の配列の最後に到達すると、head/tail の値は単純に 0 に設定されます (つまり、循環します)。
の場合head == tail
、バッファは空です。の場合inc(tail) == head
、バッファはいっぱいで、現在のコンテンツの長さは までに到達しhead <= tail ? (tail - head) : (capacity - head + tail)
ます。バッキング配列のサイズは、通常capacity+1
、tail
インデックスがバッファーがいっぱいの場合と等しくないようhead
になっています (別のフラグがないとあいまいになります)。
これにより、内部インデックスの処理が少し複雑になりますが、状態をフリップフロップする必要がなく、データを内部配列の先頭に「圧縮」する必要がないというメリットがあります (ただし、ほとんどの実装では、開始/終了インデックスを次のようにリセットします)。バッファーが空になるたびにゼロ)。
通常、これは、2 つの配列コピーが必要になる可能性があることを読み取るときに、トレードオフにもなります。最初に からhead
配列の末尾まで、次に配列の先頭から までtail
。ターゲットがバッファでもあり、書き込み中にラップする場合は、3 つのコピー操作が必要になることがあります (ただし、余分なコピーはput
メソッド内に隠されています)。
仮定
私の最善の推測は、Java がこの方法でバッファを定義し、バッファへのすべての読み取りと書き込みが連続したブロックで行われるようにすることです。これにより、おそらく、ソケット、メモリマップ、チャネルなどを処理する際にダウンストリーム/内部の最適化が可能になり、中間コピーを作成する必要がなくなります。
ここで推測するだけです。
ノート
[1] ダブル フリッピングにより制限が ではなく 0 に設定されるため、無効です。これは、 <= 0 または位置 >= 制限であるため、ほとんどの内部メソッドでcapacity
発生します。例えば:BufferOverflowException
limit - position
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 }