次の表は、Java NIO と IO の主な違いをまとめたものです。表の各部分の違いについて詳しく説明します。
次のようにコードをコピーします。
イオニオ
ストリーム指向とバッファ指向
ブロッキング IO ノンブロッキング IO
セレクターなし
ストリーム指向とバッファ指向
Java NIO と IO の最初の最大の違いは、IO がストリーム指向であるのに対し、NIO はバッファ指向であることです。 Java IO はストリーム指向であり、一度に 1 つ以上のバイトがストリームから読み取られ、すべてのバイトが読み取られるまではどこにもキャッシュされません。さらに、ストリーム内のデータを前後に移動することはできません。ストリームから読み取ったデータを前後に移動する必要がある場合は、最初にデータをバッファーにキャッシュする必要があります。 Java NIO のバッファ指向のアプローチは少し異なります。データはバッファに読み込まれ、後で処理され、必要に応じてバッファ内を行き来します。これにより、処理の柔軟性が向上します。ただし、処理する必要のあるすべてのデータがバッファーに含まれていることも確認する必要があります。また、より多くのデータがバッファに読み込まれるときに、バッファ内の未処理のデータが上書きされないように注意してください。
ブロッキング IO とノンブロッキング IO
Java IO のさまざまなストリームがブロックされています。これは、スレッドが read() または write() を呼び出すと、データが読み取られるまで、またはデータが完全に書き込まれるまで、スレッドがブロックされることを意味します。この期間中、スレッドは他のことを行うことはできません。 Java NIO のノンブロッキング モードでは、スレッドは特定のチャネルからデータを読み取るリクエストを送信できますが、現在利用可能なデータのみを取得できます。現在利用可能なデータがない場合は、何も取得されません。スレッドをブロックしたままにする代わりに、スレッドはデータが読み取り可能になるまで他の作業を続行できます。 ノンブロッキング書き込みについても同様です。スレッドはチャネルへのデータの書き込みを要求しますが、データが完全に書き込まれるまで待つ必要はありません。その間、スレッドは他の作業を行うことができます。 通常、スレッドはノンブロッキング IO のアイドル時間を他のチャネルで IO 操作を実行するために使用するため、単一のスレッドで複数の入出力チャネルを管理できるようになりました。
セレクター
Java NIO のセレクターを使用すると、単一のスレッドで複数の入力チャネルを監視できます。セレクターを使用して複数のチャネルを登録し、別のスレッドを使用してチャネルを「選択」できます。これらのチャネルには、処理可能な入力がすでに存在します。書く準備ができています。この選択メカニズムにより、単一のスレッドで複数のチャネルを簡単に管理できるようになります。
NIO と IO がアプリケーション設計に与える影響
IO ツールボックスを選択するか NIO ツールボックスを選択するかに関係なく、アプリケーションの設計に影響を与える可能性のあるいくつかの側面があります。
1. NIO または IO クラスへの API 呼び出し。
2. データ処理。
3. データの処理に使用されるスレッドの数。
API呼び出し
もちろん、NIO を使用する場合の API 呼び出しは IO を使用する場合とは異なって見えますが、InputStream からバイトごとに読み取るのではなく、まずデータをバッファーに読み取ってから処理する必要があるため、これは予期せぬことではありません。
データ処理
IO 設計と比較して純粋な NIO 設計を使用すると、データ処理も影響を受けます。
IO 設計では、InputStream または Reader からデータをバイトごとに読み取ります。たとえば、行ベースのテキスト データ ストリームを処理しているとします。
次のようにコードをコピーします。
名前:アンナ
年齢: 25歳
電子メール: [email protected]
電話: 1234567890
テキスト行のストリームは次のように処理できます。
次のようにコードをコピーします。
BufferedReader リーダー = new BufferedReader(new InputStreamReader(input));
文字列 nameLine = Reader.readLine();
文字列 ageLine = Reader.readLine();
文字列 emailLine = Reader.readLine();
文字列phoneLine = Reader.readLine();
処理ステータスはプログラムの実行時間によって決まることに注意してください。つまり、readline() メソッドが返されると、テキスト行が読み取られたことが確実にわかります。これが、行全体が読み取られるまで readline() がブロックする理由です。また、この行に名前が含まれていることもわかります。同様に、2 番目の readline() 呼び出しが返されると、この行に年齢などが含まれていることもわかります。 ご覧のとおり、このハンドラーは新しいデータが読み込まれたときにのみ実行され、各ステップでデータが何であるかを認識します。実行中のスレッドが読み取ったデータの一部を処理すると、(ほとんどの場合) データはロールバックされません。次の図もこの原理を示しています。
(Java IO: ブロッキング ストリームからのデータの読み取り) NIO の実装は異なりますが、簡単な例を次に示します。
次のようにコードをコピーします。 ByteBufferbuffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
チャネルから ByteBuffer にバイトを読み取る 2 行目に注目してください。このメソッド呼び出しが返されたとき、必要なデータがすべてバッファーにあるかどうかはわかりません。わかっているのは、バッファーにいくつかのバイトが含まれているため、処理が少し難しくなっているということだけです。
最初の read(buffer) 呼び出しの後、バッファーに読み取られたデータがわずか半行 (たとえば、「名前: An」) であるとします。そのデータを処理できますか?明らかにそうではありません。データの行全体がキャッシュに読み込まれるまで待つ必要があります。その前に、データを処理しても意味がありません。
では、バッファーに処理するのに十分なデータが含まれているかどうかをどうやって知ることができるのでしょうか?まあ、あなたにはわかりません。検出されたメソッドはバッファ内のデータのみを表示できます。その結果、すべてのデータがバッファー内にあることを確認するまでに、バッファーのデータを数回チェックする必要があります。これは非効率であるだけでなく、プログラミング ソリューションが煩雑になる可能性もあります。例えば:
次のようにコードをコピーします。
ByteBuffer バッファ = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(!bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}
bufferFull() メソッドは、バッファに読み取られたデータの量を追跡し、バッファがいっぱいかどうかに応じて true または false を返す必要があります。言い換えれば、バッファが処理できる状態であれば、バッファはいっぱいです。
bufferFull() メソッドはバッファをスキャンしますが、bufferFull() メソッドが呼び出される前と同じ状態を維持する必要があります。そうしないと、バッファに読み込まれる次のデータが正しい場所に読み込まれない可能性があります。これは不可能ですが、注意すべきもう 1 つの問題です。
バッファがいっぱいの場合は処理できます。それが機能しなくても、実際のケースでは意味がある場合は、一部は処理できる可能性があります。しかし多くの場合、そうではありません。次の図は、「バッファ データ サイクルの準備完了」を示しています。
3) データの処理に使用されるスレッドの数
NIO を使用すると、単一のスレッド (またはいくつか) を使用して複数のチャネル (ネットワーク接続またはファイル) を管理できますが、そのトレードオフとして、データの解析がブロッキング ストリームからの読み取りよりも複雑になる可能性があります。
チャット サーバーなど、同時に開かれ、毎回少量のデータのみを送信する数千の接続を管理する必要がある場合、NIO を実装したサーバーが有利になる可能性があります。同様に、P2P ネットワークなど、他のコンピューターへの多数のオープン接続を維持する必要がある場合、別のスレッドを使用してすべての送信接続を管理すると利点がある場合があります。 1 つのスレッド内の複数の接続の設計計画は次のとおりです。
Java NIO: 複数の接続を管理する単一スレッド
非常に高い帯域幅を使用し、一度に大量のデータを送信する少数の接続がある場合は、おそらく典型的な IO サーバー実装が適している可能性があります。次の図は、一般的な IO サーバーの設計を示しています。
Java IO: 一般的な IO サーバー設計 - 1 つの接続が 1 つのスレッドで処理されます。