I.概要
Bufferの本質は、実際には一般的なByteBufferに類似したメモリの一部であり、単純にByte配列として理解できます。Java NIOは、このメモリをBufferオブジェクトにカプセル化し、BufferとChannel間のデータ相互作用を容易にする一連のプロパティとメソッドを提供します。
2、使用法
FileChannel操作を見てみましょう。
try (RandomAccessFile accessFile =
new RandomAccessFile("/demo.txt", "rw");) {
// 获取 FileChannel
FileChannel channel = accessFile.getChannel();
// Buffer 分配空间
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从 channel 中读取数据到 buffer 中
int readBytes = channel.read(buffer);
System.out.println("读到 " + readBytes + " 字节");
// 判断是否到文件结尾
while (readBytes != -1) {
buffer.flip();
// 若 buffer 中还有数据
while (buffer.hasRemaining()) {
System.out.println((char) buffer.get());
}
buffer.compact();
readBytes = channel.read(buffer);
}
} catch (Exception e) {
e.printStackTrace();
}
上記の例から、Bufferを使用するには次の手順があります。
1.スペースを割り当てます
- assign(intcapacity)
Bufferは抽象クラスです。Bufferの各実装クラスは、Bufferオブジェクトをすばやくインスタンス化するのに役立つallocate(intcapacity)静的メソッドを提供します。たとえば、一般的なByteBuffer:public static ByteBuffer割当(int容量){if(容量<0)throw new IllegalArgumentException(); return new HeapByteBuffer(capacity、capacity);}このメソッドによって割り当てられたスペースは、その名前からもわかります。ヒープメモリに基づいています。 - assignDirect(intcapacity)は
、オフヒープメモリに基づいてスペースを割り当て、Bufferオブジェクトをインスタンス化します。パブリック静的のByteBuffer allocateDirect(INT容量){リターン新しいDirectByteBuffer(容量);} - wrap(byte [] array、int offset、int length)
各Buffer実装クラスは、配列をBufferインスタンスにラップするためのwrapメソッドを提供します。
2.チャネルからバッファにデータを読み取ります
- channel.read(buffer)メソッドを使用します
- buffer.put()メソッド
を使用するBufferのすべての実装は、データをBufferに入れるためのput()メソッドを提供します。
3.flip()メソッドを呼び出します
4.バッファからデータを取得します
channel.write(buffer)
buffer.get()
5. clear()メソッドまたはcompact()メソッドを呼び出します
上記のflip()、clear()、compact()メソッドの機能は何ですか?
3、ソースコード分析
データの保存に使用される配列に加えて、Bufferにはいくつかの重要なプロパティがあります。
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
// Used only by direct buffers
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
long address;
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
// ... 省略具体方法的代码
}
上記の4つの属性に基づいて、Bufferはそれらの位置を制御することによって読み取りおよび書き込み操作を完了します。バッファの使用から分析します:
1.スペースを割り当てます
ByteBuffer.allocate(10)メソッドが呼び出されてバッファーに10バイトのスペースが割り当てられると仮定して、allocate(intcapacity)のコードを見てください。
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
HeapByteBufferの構築メソッドを呼び出し、容量と制限を10に設定し、位置を0に設定します。
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
/*
hb = new byte[cap];
offset = 0;
*/
}
このときのバッファの概略図は次のとおりです。
バッファの初期状態では、位置は0で、制限と容量は9を指します。
2.バッファにデータを書き込む
3バイトのデータをバッファに書き込み、ByteBufferの実装の1つであるHeapByteBuffer#put(byte b)メソッドを確認します。
public ByteBuffer put(byte x) {
hb[ix(nextPutIndex())] = x;
return this;
}
protected int ix(int i) {
return i + offset;
}
ByteBufferのnextPutIndex()メソッドは、次に書き込まれるデータのインデックスを計算するために使用されます。
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
1バイトのデータが入力されるたびに、位置が1ずつ増加するという結論に達しました。
このとき、3つの属性の向きが次のように変わります。
データの3つのバイトを書き込んだ後、位置の次の動作可能な位置に点3、及び位置限度及び容量は変わりません。
この時点で、バッファ書き込みモードと呼ばれることを説明します。
書き込みモードでは、制限と容量は同じです。
3.バッファからデータを読み取ります
次に、バッファ内の上位3バイトのデータを読み取ります。では、これらの3バイトのデータをどのように見つけるのでしょうか。上記のBufferの使用を思い出して、データを読み取る前にBuffer#flip()メソッドを呼び出す必要があります。
public final Buffer flip() {
// 将 limit 设置为当前数据大小的下一个坐标
limit = position;
// position 设置为 0
position = 0;
// 如果有标记还原为默认值
mark = -1;
return this;
}
以上の操作により、データの開始範囲と終了範囲を取得しました。このとき、バッファの概略図は次のようになります。
この時点で、バッファ読み取りモードと呼ばれることを説明します。
読み取りモードでは、制限はバッファーの実際のサイズと同じです。
バッファの読み取りモードと書き込みモードを比較することにより、制限を制御することにより、バッファの読み取りと書き込みを柔軟に切り替えることができることがわかりました。
4. clear()メソッドとcompact()メソッド
clear()メソッドとcompact()メソッドを使用して、読み取りモードから書き込みモードに切り替えることができます。
- clear()方法public final Buffer clear(){position = 0; 制限=容量; マーク= -1; これを返します。}
clear()メソッドは、位置を0に設定し、容量を制限し、マークを-1に設定します。つまり、バッファは初期認識状態に復元されます。この時点では、データはバッファに残っていますが、どのデータが読み取られ、どのデータが読み取られていないかを判別することはできません。
- Compact()
cpmpact()メソッドは、Bufferの実装クラスにあります。例としてByteBufferのHeapByteBuffer実装を取り上げます。publicByteBuffercompact(){//元の配列をバッファシステムの先頭にコピーします。arraycopy(hb、ix(position())、hb、ix(0)、remaining()) ; //位置は最後の未読要素の後ろに設定されますposition(remaining()); //制限はcapacitylimit(capacity());に設定されます; //マークをキャンセルしますdiscardMark();これを返します;}コンパクトなコードからわかります()メソッドは、すべての未読データをバッファーの先頭にコピーしてから、最後の未読要素の後の位置を設定し、制限を容量に設定します。この時点で、Bufferは書き込みモードに切り替わりましたが、未読データは上書きされません。
5. mark()およびreset()メソッド
- mark()メソッドpublic final Buffer mark(){mark = position; return this;} mark()メソッドは、mark属性を使用して、現在のマークの添え字位置を格納します。
- reset()メソッドpublic final Buffer reset(){int m = mark; if(m <0)throw new InvalidMarkException(); position = m; return this;} reset()メソッドはpositionプロパティをmarkの値に復元します。2つの方法は互いに連携して、マークされた位置から読み取りと書き込みを行うために、位置の値を記録および復元します。
6、rewind()メソッド
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
rewind()メソッドは、位置を0に設定し、マークをクリアします。このとき、制限は変更されません。読み取りモードでは、すべてのデータを再読み取りできます。
4、結論
これでBufferについての議論は終わりです。比較的簡単な方法がいくつかあり、興味のある友達は自分でそれらについて学ぶことができます。