NIO のバッファー

以前に Channel を呼び出したコードでは、Buffer のサブクラスである ByteBuffer というクラスが使用されていました。Buffer と呼ばれるこのクラスは、高速デバイスと低速デバイス間の速度不一致の問題を解決するために特別に使用され、データベースの読み取りと書き込みの数を減らすこともできます。

入力バッファと出力バッファに分かれています。

多くの初心者は「バッファリング」と「キャッシュ」の違いを理解していません。

1. バッファは定期的にリフレッシュ、クリア、リセットなどを行う必要がありますが、これらの操作はキャッシュには必要ない場合があります。たとえば、料理をするとき、まな板は緩衝材であり、冷蔵庫は緩衝材です。冷蔵庫から鍋まで、切ったり、叩いたり、刻んだりする必要があり、毎回次の皿を空にする必要があるためです。冷蔵庫を定期的に空にしてリセットする必要はありません(停電や故障がない限り)。

2. バッファの中心的な機能は、デバイス間の速度制約を切り離し、デバイス間の「バッファ」となることですが、キャッシュは読み取りを高速化し、データベースからの再計算または再取得の数を減らすために使用されます。野菜市場ですべての料理を買うよりも、冷蔵庫に入れるほうが明らかに早いですし、調理するたびに冷蔵庫から取り出すよりも、まな板から取り出すほうが当然早いです。つまり、「食料品の買い物速度(ディスク)<冷蔵庫の取り込み速度(キャッシュ)<まな板の取り込み速度(バッファ)」という関係になります。

3. バッファは速度と書き込みに重点を置き、キャッシュは時間と読み取りに重点を置きます。まな板は野菜を切ることに重点を置き、冷蔵庫は保管に重点を置くのと同じです。

4. 現在のキャッシュは一般的に非常に大きく、TB レベル (1TB=1024GB) に達することもあります。これほど大きなバッファを持つことは不可能です (もちろん、まな板を冷蔵庫と同じくらい大きくすることもできます)。こんなの見たことない - _-!)。

将来、バッファリングとキャッシュが再び登場するときは、家庭のまな板を冷蔵庫に例えることができます。

NIO には、ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、および MappedByteBuffer の 8 種類のバッファがあります。最初の 7 つは基本的なデータ型に対応し、MappedByteBuffer はメモリ マッピングに特別に使用されます。

バッファ領域は実際にはコンテナであり、連続した配列/コレクションで構成されるコンテナです。Channel はファイルやネットワークからデータを読み取るためのチャネルを提供しますが、読み書きされるすべてのデータは Buffer を通過する必要があります。

データをバッファに書き込むプロセスは次のとおりです。

1. チャネルからバッファにデータを書き込みます: channel.read(buf)

2. バッファの put() メソッドを呼び出します: buf.put(Object)

バッファからデータを読み取るプロセスは次のとおりです。

1. バッファからチャネルにデータを読み取ります: channel.write(buf)

2. バッファの get() メソッドを呼び出します: buf.get()

読み取りと書き込みのプロセスはおそらく次のようになります。

これは昨日と同じ文です: 大きな工場で独自の RPC のようなシステムや MQ のようなミドルウェアを開発する場合は、これに習熟する必要があります。そうでなければ、理解できるので死ぬ必要はありません。実際、バッファはこれを確認するのに十分です。バッファのプロパティ、バッファの使用手順、JVM がメモリ内にバッファを作成する方法などについては、Mianba の必須コースであるはずですが、開発ではほとんど使用されません。

まだコードの中にあります。

バッファーの一般的なメソッド:

// 分配JVM间接缓冲区
ByteBuffer buffer = ByteBuffer.allocate(32);
System.out.println("buffer初始状态: " + buffer);
// 将position设回8
buffer.position(8);
System.out.println("buffer设置后状态: " + buffer);

System.out.println("测试reset ======================>>>");
// clear()方法,position将被设回0,limit被设置成capacity的值
buffer.clear();
System.out.println("buffer clear后状态: " + buffer);
// 设置这个缓冲区的位置
buffer.position(5);
// 将此缓冲区的标记设置5
// 如果没有buffer.mark();这句话会报错
buffer.mark();
buffer.position(10);
System.out.println("reset前状态: " + buffer);
// 将此缓冲区的位置重置为先前标记的位置(buffer.position(5))
buffer.reset();
System.out.println("reset后状态: " + buffer);

System.out.println("测试get ======================>>>");
buffer = ByteBuffer.allocate(32);
buffer.put((byte) 'x').put((byte) 'i').put((byte) 'a').put((byte) 'n').put((byte) 'g');
System.out.println("flip前状态: " + buffer);
// 转换为读模式
buffer.flip();
System.out.println("get前状态: " + buffer);
System.out.println((char) buffer.get());
System.out.println("get后状态: " + buffer);

System.out.println("测试put ======================>>>");
ByteBuffer pb = ByteBuffer.allocate(32);
System.out.println("put前状态: " + pb +
        ", put前数据: " + new String(pb.array()));
System.out.println("put后状态: " + pb.put((byte) 'w') +
        ", put后数据: " + new String(pb.array()));
System.out.println(pb.put(3, (byte) '3'));
// put(3, (byte) '3')并不改变position的位置,但put((byte) '3')会
System.out.println("put(3, '3')后状态: " + pb + ", 数据: " + new String(pb.array()));
// 这里的buffer是 xiang[pos=1 lim=5 cap=32]
System.out.println("buffer叠加前状态: " + buffer +
        ", buffer叠加前数据: " + new String(buffer.array()));
// buffer.put(pb);会抛异常BufferOverflowException
pb.put(buffer);
// 叠加后数据是wiang,因为buffer的position=1
System.out.println("put(buffer)后bb状态: " + pb + ", buffer叠加后数据: " + new String(pb.array()));

// 重新读取buffer中所有数据
System.out.println("测试rewind ======================>>>");
buffer.clear();
buffer.position(10);
System.out.println("buffer当前状态: " + buffer);
// 返回此缓冲区的限制
buffer.limit(15);
System.out.println("limit后状态: " + buffer);
// 把position设为0,mark设为-1,不改变limit的值
buffer.rewind();
System.out.println("rewind后状态: " + buffer);

// 将所有未读的数据拷贝到Buffer起始处,然后将position设到最后一个未读元素正后面
System.out.println("测试compact ======================>>>");
buffer.clear();
buffer.put("abcd".getBytes());
System.out.println("compact前状态: " + buffer);
System.out.println(new String(buffer.array()));
// limit=position;position=0;mark=-1; 翻转,也就是让flip之后的position到limit这块区域变成之前的0到position这块
// 翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态,或者相反
buffer.flip();
System.out.println("flip后状态: " + buffer);
// get()方法:相对读,从position位置读取一个byte,并将position+1,为下次读写作准备
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println("三次调用get后: " + buffer);
System.out.println(new String(buffer.array()));
// 把从position到limit中的内容移到0到limit-position的区域内
// position和limit的取值也分别变成limit-position、capacity
// 如果先将positon设置到limit,再compact,那么相当于clear()
buffer.compact();
System.out.println("compact后状态: " + buffer);
System.out.println(new String(buffer.array()));

Java は通常、BufferedInputStream や BufferedReader などのバッファ付き I/O クラスを使用して大きなファイルを処理しますが、ファイルが非常に大きい (GB レベルや TB レベルに達するなど) 場合は、NIO で導入されたファイル メモリ マッピング スキームを使用する方が高速な方法です。マップされたバイトバッファ。

MappedByteBuffer が必要なのは、非常に高い読み取りおよび書き込みパフォーマンスを実現することだけです。主な理由は、MappedByteBuffer が非同期操作をサポートしているためです。

大きなファイルで試してみることができます。

// ByteBuffer读取大文件
public static void useFileChannel() {
    try{
        FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
        FileChannel channel = fis.getChannel();
        long start = System.currentTimeMillis();
        ByteBuffer buff = ByteBuffer.allocate((int) channel.size());
        buff.clear();
        channel.read(buff);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        fis.close();
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// MappedByteBuffer读取大文件
public static void useMappedByteBuffer() {
    try{
        FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
        FileChannel channel = fis.getChannel();
        long start = System.currentTimeMillis();
        MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        fis.close();
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    useFileChannel();
    useMappedByteBuffer();
}

最後に、これら 2 つのメソッドを main() に入れて、効果を確認します。

NIO のバッファーだけで多くのことを説明できますが、それを感じるにはコードを使用する方がより直接的です。

おすすめ

転載: blog.csdn.net/weixin_47367099/article/details/127458562