JDKソース読解:ByteBuffer


出典:木杉のブログ、
imushan.com/2018/08/07/java/language/JDKソース読解-ByeBuffer/
BufferはJava NIOにおけるバッファに対するパッケージである.Java BIOでは,すべての読み書きAPIがbyte配列をバッファとして直接使用し,単純で直接的である.しかしJava NIOでは、バッファという概念が複雑になり、Javaスタック内のメモリに対応しているか、ローカルメモリに対応しているメモリに対応している可能性があります.byte配列はJavaスタック内のメモリを指定するためにしか使用できないので、Java NIOでは新しいバッファ抽象を設計し、異なるタイプのバッファをカバーしています.この抽象はBufferです.
Buffer
BufferはJava NIOにおけるバッファに対する抽象である.は、特定の基本データ型を格納するコンテナです.Bufferは、特定の基本データ型の線形有限シーケンスである.
Javaには8つの基本タイプがあります:byte,short,int,long,float,double,char,boolean,boolean,booleanタイプ以外のタイプには対応するBufferが具体的に実装されています.
JDKソース読解:ByteBuffer
Buffer抽象クラスは、すべてのタイプのBufferが持つ属性とアクションを定義します.属性は次のとおりです.
Capacity:バッファの容量は、バッファ確立後は変更できません
limit:最初に読み書きできない要素の位置を表し、limitはcapacityより大きくありません.
position:次の読み書きする要素の位置を表し、positionはlimitより大きくありません.
mark:エレメントの場所を一時保存し、ブックマークと同様に後続の操作に使用します.
すべてのBufferアクションは、これらのプロパティの周りで行われます.これらの属性は、0<=mark<=position<=limit<=capacityの不変式を満たす.
新しいBufferプロパティの値は次のとおりです.
position=0
Limit=capacity=ユーザ設定容量
mark=-1
定義が抽象的であることを直接見ると、概略図を見ることができます.次の図は容量10のBufferです.
JDKソース読解:ByteBuffer
ByteBufferの具体的な実装
すべてのBufferインプリメンテーションにおいて、最も重要なインプリメンテーションは、オペレーティングシステム内のすべてのIOオペレーションがバイトに対するオペレーションであるため、ByteBufferである.バイトバッファから別のデータ型を読み込む必要がある場合、他の特定のタイプのBuffer実装を使用する必要があります.
ByteBufferも抽象クラスであり、具体的な実装にはHeapByteBufferとDirectByteBufferがある.Javaヒープバッファとヒープ外メモリバッファにそれぞれ対応します.Javaスタックバッファは本質的にbyte配列であるため,実現は比較的簡単である.スタック外メモリはJNIコードの実装に関連しており、複雑である.今回はHeapByteBufferを例にBufferの関連操作を分析し、DirectByteBufferを専門に分析した.
ByteBufferのクラス図は次のとおりです.
JDKソース読解:ByteBuffer
読み書きBuffer
Bufferはバッファとして、最も主要な役割はデータを転送することです.Bufferは、一連の読み取りと書き込みを提供します.異なるタイプのBuffer読み書きのタイプが異なるため、具体的な方法定義はBuffer実装クラスに定義される.読み書きに関するAPIは以下の通りである.
byte get()
byte get(int index)
ByteBuffer get(byte[] dst, int offset, int length)
ByteBuffer get(byte[] dst)
ByteBuffer put(byte b)
ByteBuffer put(int index, byte b)
ByteBuffer put(ByteBuffer src)
ByteBuffer put(byte[] src, int offset, int length)
Bufferの読み書き操作は2つの次元で分類できます.
単一/バッチ:
シングル:1バイトずつ読み書き
一括:1回に複数バイトを読み書き
相対/絶対:
相対:Bufferメンテナンスのposition位置から読み書きを開始し、読み書き時にpositionが変化します
絶対:読み書きの場所を直接指定します.indexを指定するAPIがアブソAPI
次に、これらの関数がHeapByteBufferでどのように実現されているかを見てみましょう.
final byte[] hb;//バッファとしてのbyte配列
final int offset;//バッファの開始位置の指定
public byte get() {
//get操作は配列から直接データを取得することです
return hb[ix(nextGetIndex())];
}
public byte get(int i) {
//指定された場所からデータを取得するのは、絶対的な操作で、下付き文字が合法かどうかをチェックするだけです
return hb[ix(checkIndex(i))];
}
//次の読み込む要素の下付き文字を取得する
//positionの定義は、次に読み書きする要素の位置です.
//だからここではpositionの現在値を返し、positionを追加します
final int nextGetIndex() {//package-private
if (position >= limit)
   throw new BufferUnderflowException();

return position++;
}
//オフセット量がサポートされているので、算出した下付き文字にオフセット量を加算する必要があります
protected int ix(int i) {
return i + offset;
}
シングルバイトputはgetロジックと同じです.バッチgetがどのように実現されているかを見てみましょう.
public ByteBuffer get(byte[] dst) {
return get(dst, 0, dst.length);
}
public ByteBuffer get(byte[] dst, int offset, int length) {
//パラメータが境界を越えていないかチェック
checkBounds(offset, length, dst.length);
//取得するデータ長がBufferの残りのデータ長より大きいかチェック
if (length > remaining())
   throw new BufferUnderflowException();

//システム.arraycopyを呼び出して配列コンテンツコピーを行う
System.arraycopy(hb, ix(position()), dst, offset, length);
//positionの更新
position(position() + length);
return this;
}
HeapByteBufferはbyte配列をカプセル化した簡単な操作であることがわかる.バッファへの書き込みと読み出しは本質的に配列への書き込みと読み出しである.HeapByteBufferを使用するメリットは、さまざまなパラメータチェックを行う必要がなく、配列の現在の読み書き位置の変数を別途維持する必要がないことです.
同時に、Bufferではpositionの操作に対してロックを使用して保護されていないため、Bufferはスレッドが安全ではないことがわかります.
Bufferのモード
JDKのJava DocはBufferにモードがあるとは言っていませんが、Bufferはflipなどの操作を提供してBufferの動作モードを切り替えることができます.Bufferを正しく使用する場合は、Bufferの現在の動作モードに注意してください.そうしないと、データの読み書きがあなたの予想に合わないことになります.
Bufferには2つの動作モードがあり、1つは受信データモードであり、1つは出力データモードである.
新しいBufferはデータを受信するモードで、Bufferにデータを入れることができ、基本タイプに対応するデータを入れると、positionが1つ追加され、positionがlimitに等しい場合にput操作を行うと、BufferOverflowException異常が放出されます.
このモードのBufferは、Channelのread動作バッファに使用されてもよいし、put動作に対して使用されてもよい.
例えば、データパターンのBuffer put 5 byteを受信した後の例図:
JDKソース読解:ByteBuffer
Bufferは、読み書きの位置変数にpositionという変数を使用するように設計されているので、Bufferからデータを読み出す場合は、Bufferを出力データモードに切り替えます.Bufferは、この切り替えのためのflip方法を提供する.
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
切り替え後の効果図:
JDKソース読解:ByteBuffer
そしてBufferからデータを読み取ることができます.1つの要素を読み込むたびにpositionが1つ追加され、positionがlimitに等しい場合はBufferUnderflowException異常が放出されます.
Buffer自体にはモードを格納するための変数はなく,モードの切り替えはpositionとlimitの変換にすぎないことがわかる.
flipメソッドは、Bufferを受信モードから出力モードに切り替えるだけで、出力モードから受信モードに切り替える場合はcompactまたはclearメソッドを使用し、データの読み取りが完了したり、データが不要になったりした場合はclearメソッドを使用し、読み出したデータを保持する必要がある場合は、受信データモードに切り替える必要があり、compatメソッドを使用します.
//Bufferを圧縮し、読み込まれたデータを取り除く
//圧縮後のBufferは受信データモード
public ByteBuffer compact() {
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());
limit(capacity());
discardMark();
return this;
}
//Bufferを空にして、すべてのデータを削除します(クリーンアップ作業をしていません.位置変数を変更することです)
//クリア後のBufferは受信データモード
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
まとめ
BufferはJava NIOによるバッファの抽象である
booleanタイプを除いて、他の基本タイプには対応するBuffer実装があります.
最も一般的なBuffer実装はByteBufferであり、具体的な実装はHeapByteBufferとDirectByteBufferであり、Javaスタックバッファと対外メモリバッファにそれぞれ対応している.
HeapByteBufferはbyte配列のパッケージで使いやすい
Bufferはスレッドが安全ではありません
Bufferには、受信データモードと出力データモードの2つのモードがあります.新しいBufferは受信データモードにあり、flipメソッドを使用して出力データモードに切り替えることができます.compactまたはclearメソッドを使用して、受信データモードに切り替えることができます.