Java NIO の徹底解説

Java NIO の徹底解説

ここに画像の説明を挿入
多くの技術フレームワークで NIO テクノロジーが使用されており、Java NIO テクノロジーを学習して習得することは、高性能で同時実行性の高いネットワーク アプリケーションにとって非常に重要です。

NIO の概要

NIO 中的 N 可以理解为 Non-blocking,不单纯是 New,是解决高并发、I/O高性能的有效方式。
Java NIO是Java1.4之后推出来的一套IO接口,NIO提供了一种完全不同的操作方式, NIO支持面向缓冲区的、
基于	通道的IO操作。

入出力を処理するための新しいクラスが多数追加され、これらのクラスは java.nio パッケージおよびサブパッケージの下に配置され、元の java.io パッケージ内の多くのクラスが書き換えられ、NIO に適合する新しい関数が追加されます。
ここに画像の説明を挿入

ニオ VS バイオ

バイオ

BIO全称是Blocking IO,同步阻塞式IO,是JDK1.4之前的传统IO模型。

Java BIO: サーバーの実装モードは、接続ごとに 1 つのスレッドです。つまり、クライアントが接続要求を行うと、サーバーは、次の図に示すように、処理のためにスレッドを開始する必要があります。クライアントが要求した複数の処理を処理できます
ここに画像の説明を挿入
が、問題が発生しました。オープンされるスレッドの数が増加すると、メモリ リソースが過剰に消費され、サーバーの速度が低下したり、場合によってはクラッシュしたりします。NIO はこの問題をある程度解決できます。範囲。

NIO

Java NIO: 同期および非ブロッキング、サーバー実装モードは複数のリクエスト (接続) を処理する 1 つのスレッドです。つまり、クライアントから送信された接続リクエストはマルチプレクサーに登録され、マルチプレクサーは I/O を使用して接続をポーリングします。 O リクエストは処理されます。

1 つのスレッドは多重化インターフェイス (Java で選択) を呼び出して、複数のクライアントからの IO リクエストを同時にブロックおよび監視できます。IO リクエストが受信されると、対応する関数を呼び出して処理します。NIO は複数の接続の管理に優れています1 つのスレッドでシステム リソースを節約します。

NIO のコア実装

NIO 包含3个核心的组件:
● Channel(通道)
● Buffer(缓冲区)
● Selector(选择器)

ここに画像の説明を挿入

関係図の説明:

1. 每个 Channel 对应一个 Buffer。
2. Selector 对应一个线程,一个线程对应多个 Channel。
3. 该图反应了有三个 Channel 注册到该 Selector。
4. 程序切换到那个 Channel 是由事件决定的(Event)。
5. Selector 会根据不同的事件,在各个通道上切换。
6. Buffer 就是一个内存块,底层是有一个数组。
7. 数据的读取和写入是通过 Buffer,但是需要flip()切换读写模式,而 BIO 是单向的,要么输入流要么输出流。

チャンネル(チャンネル)

チャネルは NIO の中心概念です。オープン接続を表します。この接続は、I/O デバイス (例: ディスク ファイル、ソケット) または I/O アクセスをサポートするアプリケーションに接続できます。Java NIO はバッファとチャネルを使用しますデータ転送用に。
ここに画像の説明を挿入

チャネルの主な実装クラス:
FileChannel クラス

ローカル ファイル IO チャネルは、ファイルの読み取り、書き込み、マップ、および操作に使用されます。ファイル チャネルを使用してファイルを操作する一般的なプロセスは次のとおりです: 1) チャネルを取得します。ファイル チャネルは、静的メソッド open() を通じて取得され
ます
。 FileChannel。ファイル パスとファイルを開く方法を指定する必要があります。
// ファイル チャネルを取得 FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);

2) バイト バッファの作成
ファイル関連のバイト バッファには 2 種類あり、1 つはヒープベースの HeapByteBuffer、もう 1 つはファイル マッピングに基づいてオフヒープ メモリに配置される MappedByteBuffer です。
// バイトバッファを割り当てます ByteBuffer buf = ByteBuffer.allocate(10);

3) 読み取りおよび書き込み操作では、
通常、データを読み取るためのループ構造が必要であり、データを読み取る際の ByteBuffer の読み取りおよび書き込みモードの切り替えに注意する必要があります。

//读取数据
while (channel.read(buf) != -1){
    
     // 读取通道中的数据,并写入到 buf 中
    buf.flip(); // 缓存区切换到读模式
    while (buf.position() < buf.limit()){
    
     // 读取 buf 中的数据
        text.append((char)buf.get());
    }
    buf.clear(); // 清空 buffer,缓存区切换到写模式
}
//写入数据
for (int i = 0; i < text.length(); i++) {
    
         
  // 填充缓冲区,需要将 2 字节的 char 强转为 1 自己的 byte 
  buf.put((byte)text.charAt(i)); 
  // 缓存区已满或者已经遍历到最后一个字符
  if (buf.position() == buf.limit() || i == text.length() - 1) {
    
      
    // 将缓冲区由写模式置为读模式
    buf.flip();   
    // 将缓冲区的数据写到通道    
    channel.write(buf); 
    // 清空缓存区,将缓冲区置为写模式,下次才能使用     
    buf.clear(); 
  } }

4) データを物理ディスクにブラシします。FileChannel
の Force(boolean metaData) メソッドにより、ファイルに対する操作を確実にディスクに更新できます。
チャンネル.force(false);

5) チャンネルを閉じる
チャンネル.close();

SocketChannel クラス

ネットワーク ソケット IO チャネル、TCP プロトコル、ストリーム指向の接続ソケット用のオプションのチャネル (通常はクライアントで使用されます)。
TCP クライアントが SocketChannel を使用してサーバーと対話するプロセスは次のとおりです。

1) チャネルを開き、サーバーに接続します。
// 打开通道,此时还没有打开 TCP 连接 
SocketChannel channel = SocketChannel.open(); 
// 连接到服务端
channel.connect(new InetSocketAddress("localhost", 9090)); 
2) バッファを確保する
// 分配一个 10 字节的缓冲区,不实用,容量太小
ByteBuffer buf = ByteBuffer.allocate(10); 
3) 設定がブロッキング モードであるかどうか。(デフォルトはブロッキングモードです)
channel.configureBlocking(false); // 配置通道为非阻塞模式
4) サーバーとのデータ通信
5) 接続を閉じます
channel.close();          // 关闭通道

ServerSocketChannel クラス

ネットワーク通信 IO 操作、TCP プロトコル、ストリーム指向のリスニング ソケット (通常はサーバーに使用される) のオプションのチャネル、プロセスは次のとおりです。

1) ServerSocketChannel チャネルを開き、ポートをバインドします。
ServerSocketChannel server = ServerSocketChannel.open(); // 打开通道
2) バインドポート
server.bind(new InetSocketAddress(9090)); // 绑定端口
3) 接続をブロックして待機します。新しい接続があると、SocketChannel チャネルが作成され、サーバーはこのチャネルを通じて接続されたクライアントと通信できます。接続の到着を待機するコードは、通常、ループ構造内に配置されます。
SocketChannel client = server.accept(); // 阻塞,直到有连接过来
4) SocketChannel を介したクライアントとのデータ対話
5) SocketChannelを閉じる
client.close();

バッファ

缓冲区 Buffer 是 Java NIO 中一个核心概念,在NIO库中,所有数据都是用缓冲区处理的。
在读取数据时,它是直接读到缓冲区中的,在写入数据时,它也是写入到缓冲区中的,任何时候访问 NIO 中的数据,
都是将它放到缓冲区中。
而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。

ここに画像の説明を挿入

バッファのデータ型

ここに画像の説明を挿入

从类图中可以看到,7 种数据类型对应着 7 种子类,这些名字是 Heap 开头子类,数据是存放在 JVM 堆中的。

マップされたバイトバッファ

而 MappedByteBuffer 则是存放在堆外的直接内存中,可以映射到文件。
通过java.nio包和MappedByteBuffer允许Java程序直接从内存中读取文件内容,通过将整个或部分文件映射到内存,由操作系统来处理加载请求和写入文件,应用只需要和内存打交道,这使得IO操作非常快。
Mmap内存映射和普通标准IO操作的本质区别在于它并不需要将文件中的数据先拷贝至OS的内核IO缓冲区,而是可	以直接将用户进程私有地址空间中的一块区域与文件对象建立映射关系,这样程序就好像可以直接从内存中完成对文件读/写操作一样。

ここに画像の説明を挿入

只有当缺页中断发生时,直接将文件从磁盘拷贝至用户态的进程空间内,只进行了一次数据拷贝,对于容量较大的文件来说(文件大小一般需要限制在1.5~2G以下),采用Mmap的方式其读/写的效率和性能都非常高,大家熟知的RocketMQ就使用了该技术。

バッファデータフロー

アプリケーション プログラムは、I/O デバイスとのチャネルを確立することで、I/O デバイスへの読み取りおよび書き込み操作を実現でき、操作データはバッファー バッファーを介してやり取りされます。

I/O デバイスからデータを読み取る場合:
1)应用程序调用通道 Channel 的 read() 方法;
2)通道往缓冲区 Buffer 中填入 I/O 设备中的数据,填充完成之后返回;
3)应用程序从缓冲区 Buffer 中获取数据。
往 I/O 设备写数据时:
1)应用程序往缓冲区 Buffer 中填入要写到 I/O 设备中的数据;
2)调用通道 Channel 的 write() 方法,通道将数据传输至 I/O 设备。
バッファコア方式

バッファーアクセスデータの 2 つのコアメソッド:
1) put(): データをバッファーに保存します。

● put(byte b): 指定された 1 バイトをバッファの現在位置に書き込みます
put(byte[] src): src のバイトをバッファの現在位置に書き込みます
put(int index, byte b ):指定されたバイトをバッファのインデックス位置に書き込みます(位置は移動されません)

2) get(): バッファ内のデータを取得します。

● get(): 単一バイトを読み取ります。
● get(byte[] dst): 複数のバイトをバッチで dst に読み取ります。
● get(int Index): 指定されたインデックス位置のバイトを読み取ります (位置は移動されません)。

Selector (セレクター)
Selector クラスは NIO のコア クラスであり、Selector (セレクター) セレクターは、準備ができているタスクを選択する機能を提供します。
セレクタは、それに登録されているすべてのチャネルを継続的にポーリングします。チャネルが読み取りや書き込みなどのイベントの準備ができている場合、そのチャネルは準備完了状態になります。セレクタは継続的にポーリングして準備完了チャネルを見つけ、後続の IO 操作を実行できます。

セレクターは複数のチャネルを同時にポーリングできるため、単一のスレッドで複数のチャネルを管理でき、それによって複数のネットワーク接続を管理できるため、接続ごとにスレッドを作成する必要がなく、スレッド間のオーバーヘッドが発生することも回避されます。コンテキスト切り替えによって。

セレクターを使用する手順

1. セレクタの取得
チャネルやバッファの取得と同様に、セレクタの取得もスタティック ファクトリ メソッド open() によって取得されます。

Selector selector = Selector.open(); // 获取一个选择器实例

2 選択可能なチャネルの取得
セレクターによって監視できるチャネルは SelectableChannel インターフェイスを実装する必要があり、チャネルはノンブロッキング モードで構成されている必要があります。そうしないと、後続の登録手順で IllegalBlockingModeException がスローされます。

// 打开 SocketChannel 并连接到本机 9090 端口
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9090)); 

// チャネルをノンブロッキング モードとして設定します

 socketChannel.configureBlocking(false); 

3 セレクタへのチャネルの登録
指定されたセレクタによってチャネルが監視される前に、まずセレクタに通知し、監視対象のイベントを通知する必要があります。つまり、チャネルをセレクタに登録します。
チャネルの登録は、SelectableChannel.register(Selector selector, int ops) を通じて行われます。ops は、対象のイベントを表します。チャネルの複数の I/O イベントに注意を払う必要がある場合は、これらのイベント タイプを渡すことができます。操作の結果。これらのイベントはチャネルによってサポートされている必要があります。サポートされていない場合は、IllegalArgumentException がスローされます。
// ソケットをセレクターに登録し、読み取りイベントと書き込みイベントに注意してください

socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); 

4 選択した Ready イベントのポーリング
Ready イベントを取得するには、セレクターの Selector.select() メソッドを呼び出します。これにより、Ready イベントが SelectionKey コレクションに入れられ、Ready イベントの数が返されます。このメソッドは、多重化 I/O モデルで select システム コールをマップします。これはブロッキング メソッドです。通常の状況では、少なくとも 1 つの Ready イベントが発生するか、他のスレッドが現在の Selector オブジェクトの wakeup() メソッドを呼び出すか、現在のスレッドが中断されるまで、戻ります。

while (selector.select() > 0){
    
     
  // 轮询,且返回时有就绪事件 Set<SelectionKey> keys = selector.selectedKeys(); 
  // 获取就绪事件集合
  ....... 
}

準備完了イベントを選択するには 3 つの方法があります。

1) select() ブロッキング メソッドは、ready イベントがある場合、または他のスレッドが wakeup() を呼び出した場合、または現在のスレッドが中断された場合に戻ります。
2) select(long
timeout) ブロッキング メソッドは、ready イベントがある場合、他のスレッドが wakeup() を呼び出す場合、現在のスレッドが中断される場合、またはブロッキング期間が timeout に達する場合に戻ります
タイムアウト例外はスローされません。
3) selectNode() はブロックせず、ready イベントがない場合は 0 を返し、ready イベントがある場合は、ready イベントをコレクションに入れて、ready イベントの数を返します。

5 準備完了イベントの処理 準備
完了イベントのバッチは毎回選択できるため、これらのイベントを反復する必要があります。

for(SelectionKey key : keys){
    
    
	if(key.isWritable()){
    
     // 可写事件
		if("Bye".equals( (line = scanner.nextLine()) )){
    
    
				socketChannel.shutdownOutput();
				socketChannel.close();
				break;
		}
	buf.put(line.getBytes());
	buf.flip();
	socketChannel.write(buf);
	buf.compact();
	}
}

SelectionKey オブジェクトから、1) Ready イベントの対応するチャネル、2) Ready イベントを取得できます。この情報を使用すると、I/O 操作を簡単に実行できます。
この記事はこれで終わりです、ハハハ…

おすすめ

転載: blog.csdn.net/JDKSDD/article/details/131064398
おすすめ