Java NIO は、標準 IO とは異なる IO 動作方法を提供します。
チャネルとバッファ: 標準 IO はバイト ストリームと文字ストリームに基づいて動作しますが、NIO はチャネル (チャネル) とバッファ (バッファ) に基づいて動作します。データは常にチャネルからバッファ領域に読み取られるか、バッファからバッファに書き込まれます。チャネル。
非同期 IO: Java NIO を使用すると、たとえば、スレッドがチャネルからバッファにデータを読み取るときでも、他の処理を行うことができます。データがバッファに書き込まれると、スレッドはその処理を続行できます。バッファからチャネルへの書き込みも同様です。
セレクター: Java NIO では、複数のチャネル上のイベント (接続の開始、データの到着など) をリッスンするために使用されるセレクターの概念が導入されています。したがって、単一のスレッドは複数のデータ チャネルをリッスンできます。
Java NIO の関連知識を詳しく紹介します。
Java NIO の概要
Java NIO は次のコア部分で構成されます。
チャンネル
バッファー
セレクター
Java NIO には他にも多くのクラスやコンポーネントがありますが、私の考えでは、Channel、Buffer、Selector がコア API を構成します。 Pipe や FileLock などの他のコンポーネントは、3 つのコア コンポーネントで使用される単なるユーティリティ クラスです。したがって、この概要ではこれら 3 つのコンポーネントに焦点を当てます。他のコンポーネントについては、別の章で説明します。
チャネルとバッファ
基本的に、NIO のすべての IO はチャネルから始まります。チャンネルはストリームに似ています。 データはチャネルからバッファに読み取ることも、バッファからチャネルに書き込むこともできます。以下に図を示します。
チャネルとバッファにはいくつかの種類があります。以下は、JAVA NIO のいくつかの主要なチャネルの実装です。
ファイルチャネル
データグラムチャネル
ソケットチャネル
サーバーソケットチャネル
ご覧のとおり、これらのチャネルは、ファイル IO だけでなく、UDP および TCP ネットワーク IO もカバーしています。
これらのクラスに加えて、興味深いインターフェイスがいくつかありますが、わかりやすくするために、概要では言及しないようにしました。これらについては、このチュートリアルの関連する他の章で説明します。
Java NIO の主要なバッファ実装は次のとおりです。
バイトバッファ
文字バッファ
ダブルバッファ
フロートバッファ
IntBuffer
ロングバッファ
ショートバッファ
これらのバッファは、IO 経由で送信できる基本的なデータ型 (byte、short、int、long、float、double、char) をカバーします。
Java NIO には、メモリ マップされたファイルを表すために使用される Mappyteuffer もあります。概要では説明しません。
セレクタ
セレクターを使用すると、単一のスレッドで複数のチャネルを処理できるようになります。アプリケーションが複数の接続 (チャネル) を開いているものの、各接続のトラフィックが非常に低い場合は、Selector を使用すると便利です。たとえば、チャット サーバー内です。
これは、セレクターを使用して 1 つのスレッドで 3 つのチャネルを処理する例です。
セレクターを使用するには、セレクターにチャネルを登録し、その select() メソッドを呼び出す必要があります。このメソッドは、登録されたチャネルでイベントの準備ができるまでブロックされます。このメソッドが返されると、スレッドはこれらのイベントを処理できるようになります。イベントの例としては、新しい接続の受信、データの受信などが挙げられます。
Java NIO と IO の比較
(この部分の原文アドレス、著者: Jakob Jenkov、翻訳者: Guo Lei、校正者: Fang Tengfei)
Java NIO と IO API について学んだ後、すぐに次のような疑問が頭に浮かびました。
引用
いつ IO を使用し、いつ NIO を使用する必要がありますか?この記事では、Java NIO と IO の違い、その使用シナリオ、およびそれらがコード設計に与える影響について明確に説明します。
Java NIO と IO の主な違い
次の表は、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 ツールボックスを選択するかに関係なく、アプリケーションの設計に影響を与える可能性のあるいくつかの側面があります。
NIO または IO クラスへの API 呼び出し。
データ処理。
データの処理に使用されるスレッドの数。
API呼び出し
もちろん、NIO を使用する場合の API 呼び出しは IO を使用する場合とは異なって見えますが、InputStream からバイトごとに読み取るのではなく、まずデータをバッファーに読み取ってから処理する必要があるため、これは予期せぬことではありません。
データ処理
IO 設計と比較して純粋な NIO 設計を使用すると、データ処理も影響を受けます。
IO 設計では、InputStream または Reader からデータをバイトごとに読み取ります。たとえば、行ベースのテキスト データ ストリームを処理しているとします。
次のようにコードをコピーします。
名前:アンナ
年齢: 25歳
電子メール: [email protected]
電話: 1234567890
テキスト行のストリームは次のように処理できます。
次のようにコードをコピーします。
InputStream input = … // クライアントソケットからInputStreamを取得します。
BufferedReader リーダー = new BufferedReader(new InputStreamReader(input));
文字列 nameLine = Reader.readLine();
文字列 ageLine = Reader.readLine();
文字列 emailLine= Reader.readLine();
文字列phoneLine= Reader.readLine();
処理ステータスはプログラムの実行時間によって決まることに注意してください。つまり、readline() メソッドが返されると、テキスト行が読み取られたことが確実にわかります。これが、行全体が読み取られるまで readline() がブロックする理由です。また、この行に名前が含まれていることもわかります。同様に、2 番目の readline() 呼び出しが返されると、この行に年齢などが含まれていることもわかります。 ご覧のとおり、このハンドラーは新しいデータが読み込まれたときにのみ実行され、各ステップでデータが何であるかを認識します。実行中のスレッドが読み取ったデータの一部を処理すると、(ほとんどの場合) データはロールバックされません。次の図もこの原理を示しています。
ブロックされたストリームからのデータの読み取り
NIO の実装は異なりますが、簡単な例を次に示します。
次のようにコードをコピーします。
ByteBuffer バッファ = 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 つの問題です。
バッファがいっぱいの場合は処理できます。それが機能しなくても、実際のケースでは意味がある場合は、一部は処理できる可能性があります。しかし多くの場合、そうではありません。次の図は、「バッファ データ サイクルの準備完了」を示しています。
すべてのデータがバッファに読み込まれるまで、チャネルからデータを読み取ります
要約する
NIO を使用すると、単一のスレッド (またはいくつか) を使用して複数のチャネル (ネットワーク接続またはファイル) を管理できますが、そのトレードオフとして、データの解析がブロッキング ストリームからの読み取りよりも複雑になる可能性があります。
チャット サーバーなど、同時に開かれ、毎回少量のデータのみを送信する数千の接続を管理する必要がある場合、NIO を実装したサーバーが有利になる可能性があります。同様に、P2P ネットワークなど、他のコンピューターへの多数のオープン接続を維持する必要がある場合、別のスレッドを使用してすべての送信接続を管理すると利点がある場合があります。 1 つのスレッド内の複数の接続の設計スキームを次の図に示します。
単一スレッドで複数の接続を管理
非常に高い帯域幅を使用し、一度に大量のデータを送信する少数の接続がある場合は、おそらく典型的な IO サーバー実装が適している可能性があります。次の図は、一般的な IO サーバーの設計を示しています。
一般的な IO サーバーの設計は次のとおりです。
接続はスレッドによって処理されます
チャネル
Java NIO チャネルはストリームに似ていますが、若干異なります。
チャネルからデータを読み取ったり、チャネルにデータを書き込んだりできます。ただし、ストリームの読み取りと書き込みは通常一方向です。
チャネルは非同期で読み書きできます。
チャネル内のデータは、最初にバッファから読み取るか、常にバッファから書き込む必要があります。
前述したように、データはチャネルからバッファに読み取られ、データはバッファからチャネルに書き込まれます。以下に示すように:
チャネルの実装
Java NIO の最も重要なチャネルの実装は次のとおりです。
FileChannel: ファイルからのデータの読み取りと書き込み。
DatagramChannel: UDP を介してネットワーク内のデータを読み書きできます。
SocketChannel: TCP 経由でネットワーク内のデータを読み書きできます。
ServerSocketChannel: Web サーバーのように、受信 TCP 接続を監視できます。 SocketChannel は、新しい受信接続ごとに作成されます。
基本的なチャネルの例
以下は、FileChannel を使用してデータをバッファに読み取る例です。
次のようにコードをコピーします。
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
buf.flip() の呼び出しでは、最初にデータがバッファに読み込まれ、次にバッファが反転され、次にバッファからデータが読み取られることに注意してください。次のセクションでは、バッファについて詳しく説明します。
バッファ
Java NIO のバッファは、NIO チャネルと対話するために使用されます。ご存知のとおり、データはチャネルからバッファに読み取られ、バッファからチャネルに書き込まれます。
バッファは本質的に、データを書き込んだり、そこからデータを読み取ったりできるメモリのブロックです。このメモリは NIO バッファ オブジェクトとしてパッケージ化されており、このメモリに簡単にアクセスするための一連のメソッドを提供します。
バッファの基本的な使い方
Buffer を使用してデータの読み取りと書き込みを行うには、通常、次の 4 つの手順に従います。
バッファへのデータの書き込み
フリップ()メソッドを呼び出す
バッファからデータを読み取る
clear()メソッドまたはcompact()メソッドを呼び出します。
データがバッファに書き込まれると、バッファには書き込まれたデータの量が記録されます。データを読み取りたい場合は、flip() メソッドを通じてバッファを書き込みモードから読み取りモードに切り替える必要があります。読み取りモードでは、以前にバッファに書き込まれたすべてのデータを読み取ることができます。
すべてのデータが読み取られたら、再び書き込むことができるようにバッファをクリアする必要があります。バッファをクリアするには 2 つの方法があります。clear() メソッドまたは Compact() メソッドを呼び出すことです。 clear() メソッドはバッファ全体をクリアします。 Compact() メソッドは、読み取られたデータのみをクリアします。未読のデータはバッファの先頭に移動され、新しく書き込まれたデータはバッファ内の未読のデータの後に配置されます。
以下はバッファの使用例です。
次のようにコードをコピーします。
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//容量48バイトのバッファを作成
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //バッファに読み込みます。
while (bytesRead != -1) {
buf.flip();//バッファを読み取り準備状態にする
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // 一度に 1 バイトを読み取ります
}
buf.clear(); //バッファを書き込み準備状態にする
bytesRead = inChannel.read(buf);
}
aFile.close();
バッファの容量、位置、制限
バッファは本質的に、データを書き込んだり、そこからデータを読み取ったりできるメモリのブロックです。このメモリは NIO バッファ オブジェクトとしてパッケージ化されており、このメモリに簡単にアクセスするための一連のメソッドを提供します。
Buffer がどのように機能するかを理解するには、その 3 つのプロパティを理解する必要があります。
容量
位置
限界
位置と制限の意味は、バッファが読み取りモードであるか書き込みモードであるかによって異なります。バッファーがどのモードであっても、容量の意味は常に同じです。
ここでは、読み取りおよび書き込みモードの容量、位置、および制限について、図に続いて詳細な説明を示します。
容量
メモリ ブロックとして、バッファには「容量」とも呼ばれる固定サイズの値があり、byte、long、char などの型の容量のみを書き込むことができます。バッファがいっぱいになると、データの書き込みを続行する前に (データの読み取りまたはデータのクリアによって) バッファを空にする必要があります。
位置
データをバッファに書き込むとき、position は現在の位置を表します。初期位置値は 0 です。バイト、ロングなどのデータがバッファに書き込まれると、位置はデータを挿入できる次のバッファ ユニットに進みます。最大ポジションは容量 1 です。
データが読み取られるときは、特定の場所からも読み取られます。バッファを書き込みモードから読み取りモードに切り替えると、位置は 0 にリセットされます。データがバッファの位置から読み取られると、その位置は次の読み取り可能な位置に進みます。
限界
書き込みモードでは、バッファの制限は、バッファに書き込むことができるデータの最大量を示します。 書き込みモードでは、制限はバッファの容量と同じになります。
バッファを読み取りモードに切り替える場合、limit は読み取ることができるデータの最大量を示します。したがって、バッファを読み取りモードに切り替えると、書き込みモードの位置値に制限が設定されます。つまり、以前に書き込まれたすべてのデータを読み取ることができます(制限は書き込まれたデータの数に設定されており、この値は書き込みモードの位置です)
バッファタイプ
Java NIO には次のバッファ タイプがあります。
バイトバッファ
マップされたバイトバッファ
文字バッファ
ダブルバッファ
フロートバッファ
IntBuffer
ロングバッファ
ショートバッファ
ご覧のとおり、これらのバッファー タイプはさまざまなデータ タイプを表します。つまり、バッファ内のバイトは、char、short、int、long、float、または double 型を通じて操作できます。
MappedByteBuffer は少し特殊なので、別の章で説明します。
バッファ割り当て
Buffer オブジェクトを取得するには、まずそれを割り当てる必要があります。 すべての Buffer クラスには、allocate メソッドがあります。以下は、48 バイトの容量を持つ ByteBuffer を割り当てる例です。
次のようにコードをコピーします。
ByteBuffer buf = ByteBuffer.allocate(48);
これにより、1024 文字を格納できる CharBuffer が割り当てられます。
次のようにコードをコピーします。
CharBuffer buf = CharBuffer.allocate(1024);
バッファへのデータの書き込み
データをバッファに書き込む方法は 2 つあります。
チャネルからバッファに書き込みます。
Buffer の put() メソッドを通じて Buffer に書き込みます。
チャネルからバッファへの書き込み例
次のようにコードをコピーします。
int bytesRead = inChannel.read(buf); //バッファに読み込みます。
Buffer through put メソッドの記述例:
次のようにコードをコピーします。
buf.put(127);
put メソッドには多くのバージョンがあり、さまざまな方法でデータをバッファに書き込むことができます。たとえば、指定された場所に書き込む、またはバッファにバイト配列を書き込むなどです。 バッファ実装の詳細については、JavaDoc を参照してください。
フリップ()メソッド
フリップメソッドは、バッファを書き込みモードから読み取りモードに切り替えます。 flick() メソッドを呼び出すと、位置が 0 に戻り、制限が前の位置の値に設定されます。
言い換えれば、position は読み取り位置をマークするために使用されるようになり、limit は以前に書き込まれたバイト、文字などの数、つまり現在読み取ることができるバイト、文字などの数を表します。
バッファからデータを読み取る
バッファからデータを読み取るには 2 つの方法があります。
バッファからチャネルにデータを読み取ります。
get() メソッドを使用して、バッファからデータを読み取ります。
バッファからチャネルへのデータの読み取りの例:
次のようにコードをコピーします。
// バッファからチャネルに読み込みます。
int bytesWritten = inChannel.write(buf);
get() メソッドを使用してバッファからデータを読み取る例
次のようにコードをコピーします。
バイトaByte = buf.get();
get メソッドには多くのバージョンがあり、さまざまな方法でバッファからデータを読み取ることができます。たとえば、指定された位置から読み取るか、バッファからバイト配列にデータを読み取ります。バッファ実装の詳細については、JavaDoc を参照してください。
rewind() メソッド
Buffer.rewind() は位置を 0 に戻すので、バッファ内のすべてのデータを再読み取りできます。制限は変更されず、バッファから読み取れる要素 (バイト、文字など) の数を示します。
Clear() メソッドと Compact() メソッド
バッファ内のデータが読み取られた後は、バッファに再び書き込む準備ができている必要があります。これは、clear() メソッドまたは Compact() メソッドを介して実行できます。
clear() メソッドが呼び出されると、位置は 0 に戻され、制限は容量の値に設定されます。つまり、バッファがクリアされます。バッファ内のデータはクリアされませんが、これらのマークは、バッファへのデータの書き込みを開始する場所を示します。
バッファ内に未読のデータがあり、clear() メソッドを呼び出すと、データは「忘れられます」。つまり、どのデータが読み取られ、どのデータが読み取られていないのかを示すマーカーが存在しなくなります。
バッファ内にまだ読み取られていないデータがあり、そのデータが後で必要になるが、最初にデータを書き込みたい場合は、compact() メソッドを使用します。
Compact() メソッドは、すべての未読データをバッファーの先頭にコピーします。次に、位置を最後の未読要素のすぐ後ろに設定します。制限属性は、clear() メソッドと同様に容量に設定されます。これでバッファはデータを書き込む準備が整いましたが、読み取られていないデータは上書きされません。
mark() メソッドとreset() メソッド
Buffer.mark() メソッドを呼び出すことで、バッファ内の特定の位置をマークできます。後で Buffer.reset() メソッドを呼び出すことで、この位置に復元できます。例えば:
次のようにコードをコピーします。
バッファ.マーク();
//たとえば解析中などに、buffer.get() を数回呼び出します。
buffer.reset();//位置をマークに戻します。
equals() メソッドと CompareTo() メソッド
2 つのバッファに対して、equals() メソッドと CompareTo() メソッドを使用できます。
等しい()
次の条件が満たされる場合、2 つのバッファーが等しいことを意味します。
同じ型 (byte、char、int など) であること。
バッファ内の残りのバイト、文字などの数は等しいです。
バッファ内の残りのバイト、文字などはすべて同じです。
ご覧のとおり、equals はバッファ内のすべての要素ではなく、バッファの一部のみを比較します。実際には、バッファ内の残りの要素を比較するだけです。
CompareTo() メソッド
CompareTo() メソッドは、2 つのバッファーの残りの要素 (byte、char など) を比較します。次の条件が満たされる場合、一方のバッファーは他方のバッファーより「小さい」とみなされます。
最初の不等要素は、他のバッファ内の対応する要素よりも小さいです。
すべての要素は等しいですが、最初のバッファが他のバッファよりも先に使い果たされます (最初のバッファの要素が他のバッファよりも少ない)。
(注釈: 残りの要素は位置から限界までの要素です)
散らばる・集まる
(この部分の元のアドレス、著者: Jakob Jenkov、翻訳者: Guo Lei)
Java NIO は、Scatter/Gather のサポートを開始します。Scatter/Gather は、チャネルへの読み取りまたは書き込みの操作を記述するために使用されます (翻訳者注: チャネルは、中国語ではチャネルと訳されることがよくあります)。
チャネルからの分散読み取りとは、読み取り操作中に読み取りデータを複数のバッファに書き込むことを意味します。したがって、チャネルは、チャネルから読み取られたデータを複数のバッファに「分散」します。
チャネルへの収集と書き込みは、書き込み操作中に複数のバッファから同じチャネルにデータを書き込むことを意味します。したがって、チャネルは複数のバッファ内のデータを「収集」し、それらをチャネルに送信します。
スキャッター/ギャザーは、送信データを個別に処理する必要がある状況でよく使用されます。たとえば、メッセージ ヘッダーとメッセージ本文で構成されるメッセージを送信する場合、メッセージ本文とメッセージ ヘッダーを別のバッファーに分散できます。メッセージヘッダーとメッセージ本文を簡単に処理できます。
散乱読み取り
分散読み取りとは、1 つのチャネルから複数のバッファにデータを読み取ることを指します。以下の図で説明されているように:
コード例は次のとおりです。
次のようにコードをコピーします。
ByteBuffer ヘッダー = ByteBuffer.allocate(128);
ByteBuffer 本体 = ByteBuffer.allocate(1024);
ByteBuffer[]bufferArray = {ヘッダー,ボディ};
チャネル.読み取り(バッファ配列);
まずバッファが配列に挿入され、次にその配列が channel.read() への入力パラメータとして使用されることに注意してください。 read() メソッドは、チャネルから読み取られたデータを配列内のバッファの順序でバッファに書き込みます。1 つのバッファがいっぱいになると、チャネルは別のバッファに書き込みます。
Scattering Read は、次のバッファに移動する前に現在のバッファを埋める必要があります。これは、動的メッセージには適していないことも意味します (翻訳者注: メッセージ サイズは固定されていません)。つまり、メッセージ ヘッダーとメッセージ本文がある場合、分散読み取りが適切に機能するには、メッセージ ヘッダーが完全に埋められる (たとえば、128 バイト) 必要があります。
書き込みの収集
書き込みを集めるとは、データが複数のバッファから同じチャネルに書き込まれることを意味します。以下の図で説明されているように:
コード例は次のとおりです。
次のようにコードをコピーします。
ByteBuffer ヘッダー = ByteBuffer.allocate(128);
ByteBuffer 本体 = ByteBuffer.allocate(1024);
// データをバッファに書き込みます
ByteBuffer[]bufferArray = {ヘッダー,ボディ};
チャンネル.write(バッファ配列);
バッファ配列は、write() メソッドの入力パラメータです。write() メソッドは、配列内のバッファの順序でデータをチャネルに書き込みます。位置と制限の間のデータのみが書き込まれることに注意してください。したがって、バッファの容量が 128 バイトであっても、58 バイトのデータしか含まれていない場合、58 バイトのデータがチャネルに書き込まれます。したがって、分散読み取りとは対照的に、収集書き込みは動的メッセージをより適切に処理できます。
チャネル間のデータ転送
(この部分の元のアドレス、著者: Jakob Jenkov、翻訳者: Guo Lei、校正者: Zhou Tai)
Java NIO では、2 つのチャネルのうちの 1 つが FileChannel の場合、1 つのチャネル (翻訳者注: チャネルは中国語ではチャネルと訳されることがよくあります) から別のチャネルにデータを直接転送できます。
transferFrom()
FileChannel の transferFrom() メソッドは、ソース チャネルから FileChannel にデータを転送できます (翻訳者注: このメソッドは、JDK ドキュメントでは、指定された読み取り可能なバイト チャネルからこのチャネルのファイルにバイトを転送すると説明されています)。簡単な例を次に示します。
次のようにコードをコピーします。
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
ロングポジション = 0;
長いカウント = fromChannel.size();
toChannel.transferFrom(位置, カウント, fromChannel);
メソッドの入力パラメータのpositionは、ターゲットファイルにデータを書き込む開始位置を示し、countは転送される最大バイト数を示します。ソース チャネルの残りスペースが count バイト未満の場合、転送されるバイト数は要求されたバイト数よりも少なくなります。
さらに、SoketChannel の実装では、SocketChannel は現時点で準備されているデータ (count バイト未満の可能性があります) のみを送信することに注意してください。したがって、SocketChannel は、要求されたデータ (カウント バイト) のすべてを FileChannel に転送できない場合があります。
transferTo()
transferTo() メソッドは、FileChannel から他のチャネルにデータを転送します。簡単な例を次に示します。
次のようにコードをコピーします。
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
ロングポジション = 0;
長いカウント = fromChannel.size();
fromChannel.transferTo(位置, カウント, toChannel);
この例は前の例と特に似ていることがわかりましたか?メソッドを呼び出す FileChannel オブジェクトが異なることを除いて、その他はすべて同じです。
SocketChannel に関する上記の問題は、transferTo() メソッドにも存在します。 SocketChannel は、ターゲット バッファがいっぱいになるまでデータを送信し続けます。
セレクタ
(このセクションの原文へのリンク、著者: Jakob Jenkov、翻訳者: Langjiv、校正者: Ding Yi)
セレクターは、1 つ以上の NIO チャネルを検出し、そのチャネルが読み取りや書き込みなどのイベントの準備ができているかどうかを知ることができる Java NIO のコンポーネントです。このようにして、単一のスレッドで複数のチャネルを管理できるため、複数のネットワーク接続を管理できます。
(1)セレクターを使用する理由
単一のスレッドのみを使用して複数のチャネルを処理する利点は、チャネルの処理に必要なスレッドが少なくなることです。実際、1 つのスレッドだけを使用してすべてのチャネルを処理することも可能です。オペレーティング システムの場合、スレッド間のコンテキストの切り替えは非常にコストがかかり、各スレッドは一部のシステム リソース (メモリなど) を占有します。したがって、使用するスレッドは少ないほど良いのです。
ただし、最新のオペレーティング システムと CPU はマルチタスク処理においてますます優れているため、マルチスレッドのオーバーヘッドは時間の経過とともにますます小さくなっていることに注意してください。実際、CPU に複数のコアがある場合、マルチタスクを使用しないと CPU パワーが無駄になる可能性があります。いずれにせよ、そのデザインについての議論は別の記事で行う必要があります。ここでは、Selector を使用して複数のチャネルを処理できることを知っていれば十分です。
以下は、セレクターを使用して 3 つのチャネルを処理する単一スレッドの図の例です。
(2)セレクタの作成
次のように Selector.open() メソッドを呼び出してセレクターを作成します。
次のようにコードをコピーします。
セレクター selector = Selector.open();
(3)セレクターでチャンネルを登録する
チャンネルとセレクターを併用するには、セレクターにチャンネルを登録する必要があります。これは、次のように SelectableChannel.register() メソッドを通じて実現されます。
次のようにコードをコピーします。
Channel.configureBlocking(false);
SelectionKey キー = チャネル.レジスタ(セレクタ,
選択キー.OP_READ);
セレクターと一緒に使用する場合、チャンネルはノンブロッキング モードである必要があります。これは、FileChannel を非ブロッキング モードに切り替えることができないため、FileChannel をセレクターとともに使用できないことを意味します。ソケットチャネルは問題ありません。
register() メソッドの 2 番目のパラメータに注目してください。これは「興味のあるコレクション」であり、セレクターを通じてチャンネルを聞いているときにどのようなイベントに興味があるかを意味します。リッスンできるイベントには 4 つの異なるタイプがあります。
接続する
受け入れる
読む
書く
イベントをトリガーするチャネルは、イベントの準備ができていることを意味します。したがって、別のサーバーに正常に接続されたチャネルは、「接続準備完了」と呼ばれます。サーバー ソケット チャネルは、受信接続を受信する準備ができているとき、「受信準備ができている」と言われます。読み取るデータがあるチャネルは「読み取り準備完了」であると言われます。データの書き込みを待機しているチャネルは、「書き込み準備完了」であると言えます。
これら 4 つのイベントは、SelectionKey の 4 つの定数によって表されます。
選択キー.OP_CONNECT
選択キー.OP_ACCEPT
選択キー.OP_READ
選択キー.OP_WRITE
複数のイベントに興味がある場合は、次のようにビットごとの OR 演算子を使用して定数を接続できます。
次のようにコードをコピーします。
int InterestSet = 選択キー.OP_READ | 選択キー.OP_WRITE;
利息の徴収については後述します。
(4)選択キー
前のセクションでは、セレクターにチャネルを登録するときに、 register() メソッドは SelectionKey オブジェクトを返しました。このオブジェクトには、興味のあるプロパティがいくつか含まれています。
利息の徴収
準備ができたコレクション
チャネル
セレクタ
追加のオブジェクト (オプション)
以下にこれらのプロパティについて説明します。
利息の徴収
「セレクターへのチャネルの登録」セクションで説明したように、関心のあるコレクションは、選択した興味深いイベントのコレクションです。次のように、SelectionKey を使用して関心のあるコレクションの読み取りと書き込みを行うことができます。
次のようにコードをコピーします。
intinterestSet =selectionKey.interess();
boolean isInterestedInAccept= (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
ブール値 isInterestedInConnect = InterestSet & SelectionKey.OP_CONNECT;
ブール値 isInterestedInRead = InterestSet & SelectionKey.OP_READ;
ブール値 isInterestedInWrite = InterestSet & SelectionKey.OP_WRITE;
「ビット AND」を使用して対象コレクションと指定された SelectionKey 定数を操作することにより、特定のイベントが対象コレクションに含まれるかどうかを判断できることがわかります。
準備ができたコレクション
準備完了セットは、チャネルの準備が完了している操作のセットです。選択 (Selection) 後、まずレディ セットにアクセスします。選択については次のセクションで説明します。準備ができたコレクションには次のようにアクセスできます。
intreadySet =selectionKey.readyOps();
インタレスト コレクションの検出と同じ方法を使用して、チャネル内で準備ができているイベントまたは操作を検出できます。ただし、次の 4 つのメソッドも使用でき、いずれもブール型を返します。
次のようにコードをコピーします。
selectionKey.isAcceptable();
選択キー.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
チャンネル+セレクター
SelectionKey からチャンネルとセレクターにアクセスするのは簡単です。次のように:
次のようにコードをコピーします。
Channelchannel=selectionKey.channel();
セレクター selector =selectionKey.selector();
追加のオブジェクト
特定のチャンネルを簡単に識別するために、オブジェクトまたはその他の情報をSelectionKeyに添付できます。たとえば、チャネルで使用するバッファや、集約されたデータを含むオブジェクトをアタッチできます。使用方法:
次のようにコードをコピーします。
selectionKey.attach(theObject);
オブジェクトattachedObj =selectionKey.attachment();
register() メソッドを使用してチャネルをセレクターに登録するときにオブジェクトをアタッチすることもできます。のように:
次のようにコードをコピーします。
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
(5) セレクターでチャンネルを選択します
1 つ以上のチャネルがセレクターに登録されると、いくつかのオーバーロードされた select() メソッドを呼び出すことができます。これらのメソッドは、関心のあるイベント (接続、受け入れ、読み取り、書き込みなど) の準備ができているチャネルを返します。言い換えれば、「読み取り準備完了」チャネルに興味がある場合、select() メソッドは読み取りイベントの準備ができているチャネルを返します。
select() メソッドは次のとおりです。
int select()
int select(長いタイムアウト)
int selectNow()
select() は、登録したイベントに対して少なくとも 1 つのチャネルの準備ができるまでブロックします。
select(long timeout) は select() と同じですが、タイムアウト ミリ秒 (パラメーター) までブロックされる点が異なります。
selectNow() はブロックせず、どのチャネルの準備ができていてもすぐに戻ります (翻訳者注: このメソッドは非ブロック選択操作を実行します。前の選択操作以降に選択可能になったチャネルがない場合、このメソッドは直接 0 を返します)。
select() メソッドによって返される int 値は、準備ができているチャネルの数を示します。つまり、最後に select() メソッドを呼び出してから準備完了になったチャネルの数です。 select() メソッドを呼び出すと、1 つのチャネルが準備完了になるため 1 が返されます。再度 select() メソッドを呼び出すと、別のチャネルが準備完了すると、再び 1 が返されます。最初の準備完了チャネルで何も操作が行われない場合、準備完了チャネルは 2 つになりますが、各 select() メソッド呼び出しの間に準備完了状態になるチャネルは 1 つだけです。
selectedKeys()
select() メソッドが呼び出され、戻り値が 1 つ以上のチャネルの準備ができていることを示すと、セレクターの selectedKeys() メソッドを呼び出すことによって、「選択されたキー セット」内の準備ができたチャネルにアクセスできます。以下に示すように:
次のようにコードをコピーします。
selectedKeys = selector.selectedKeys(); を設定します。
セレクターなどのチャネルを登録する場合、Channel.register() メソッドは SelectionKey オブジェクトを返します。このオブジェクトは、セレクターに登録されているチャンネルを表します。これらのオブジェクトには、SelectionKey の selectedKeySet() メソッドを通じてアクセスできます。
準備完了チャネルには、この選択されたキーのセットを移動することによってアクセスできます。次のように:
次のようにコードをコピーします。
selectedKeys = selector.selectedKeys(); を設定します。
イテレータ keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// 接続は ServerSocketChannel によって受け入れられました。
else if (key.isConnectable()) {
// リモートサーバーとの接続が確立されました。
else if (key.isReadable()) {
// チャネルを読み取る準備ができました
else if (key.isWritable()) {
// チャネルは書き込みの準備ができています
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">削除</a ></tuihighlight>();
}
このループは、選択されたキー セット内の各キーを反復処理し、各キーに対応するチャネルの Ready イベントを検出します。
各反復の最後に keyIterator.remove() が呼び出されていることに注意してください。セレクターは、選択されたキー セット自体から SelectionKey インスタンスを削除しません。チャンネルが処理されるときに自分で削除する必要があります。次回チャンネルの準備が整うと、セレクターはそのチャンネルを選択したキー セットに再び入れます。
SelectionKey.channel() メソッドによって返されるチャネルは、ServerSocketChannel や SocketChannel など、処理するタイプに変換する必要があります。
(6)ウェイクアップ()
select() メソッドの呼び出し後にスレッドがブロックされる場合でも、準備ができているチャネルがない場合でも、select() メソッドからそれを返す方法があります。最初のスレッドが select() メソッドを呼び出したオブジェクトに対して、他のスレッドが Selector.wakeup() メソッドを呼び出すようにするだけです。 select() メソッドでブロックされたスレッドはすぐに戻ります。
別のスレッドが wakeup() メソッドを呼び出しても、現在 select() メソッドでブロックされているスレッドがない場合、select() メソッドを呼び出す次のスレッドがすぐに「ウェイクアップ」します。
(7)閉じる()
セレクターを使用した後に close() メソッドを呼び出すと、セレクターが閉じられ、セレクターに登録されているすべての SelectionKey インスタンスが無効になります。チャンネル自体は閉じません。
(8) 完全な例
以下に完全な例を示します。セレクターを開き、セレクターにチャネルを登録し (チャネルの初期化プロセスは省略されます)、セレクターの 4 つのイベント (受け入れ、接続、読み取り、書き込み) の準備ができているかどうかを継続的に監視します。
次のようにコードをコピーします。
セレクター selector = Selector.open();
Channel.configureBlocking(false);
選択キー キー = チャンネル.レジスタ(セレクター, 選択キー.OP_READ);
while(true) {
intreadyChannels = selector.select();
if(readyChannels == 0) 続行;
selectedKeys = selector.selectedKeys(); を設定します。
イテレータ keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// 接続は ServerSocketChannel によって受け入れられました。
else if (key.isConnectable()) {
// リモートサーバーとの接続が確立されました。
else if (key.isReadable()) {
// チャネルを読み取る準備ができました
else if (key.isWritable()) {
// チャネルは書き込みの準備ができています
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;">削除</a ></tuihighlight>();
}
}
ファイルチャンネル
(この節の原文へのリンク、著者: Jakob Jenkov、翻訳者: Zhou Tai、校正者: Ding Yi)
Java NIO の FileChannel は、ファイルに接続されたチャネルです。ファイルは、ファイル チャネルを通じて読み書きできます。
FileChannel は非ブロッキング モードに設定できません。常にブロッキング モードで実行されます。
オープンファイルチャネル
FileChannel を使用する前に、FileChannel を開く必要があります。ただし、FileChannel を直接開くことはできません。InputStream、OutputStream、または RandomAccessFile を使用して FileChannel インスタンスを取得する必要があります。 RandomAccessFile 経由で FileChannel を開く例を次に示します。
次のようにコードをコピーします。
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
FileChannel からデータを読み取る
複数の read() メソッドの 1 つを呼び出して、FileChannel からデータを読み取ります。のように:
次のようにコードをコピーします。
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
まず、バッファを割り当てます。 FileChannel から読み取られたデータは Buffer に読み込まれます。
次に、FileChannel.read() メソッドを呼び出します。このメソッドは、FileChannel から Buffer にデータを読み取ります。 read() メソッドによって返される int 値は、バッファに読み取られたバイト数を示します。 -1 が返された場合は、ファイルの終わりに達したことを意味します。
FileChannel へのデータの書き込み
FileChannel.write() メソッドを使用して、データを FileChannel に書き込みます。このメソッドのパラメータは Buffer です。のように:
次のようにコードをコピーします。
String newData = "ファイルに書き込む新しい文字列..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
チャネル書き込み(buf);
}
FileChannel.write() は while ループで呼び出されることに注意してください。 write() メソッドが一度に FileChannel に書き込めるバイト数は保証されないため、バッファー内にチャネルに書き込まれていないバイトがなくなるまで、 write() メソッドを繰り返し呼び出す必要があります。
ファイルチャネルを閉じる
FileChannel を使い終わったら、FileChannel を閉じる必要があります。のように:
次のようにコードをコピーします。
チャンネル.close();
FileChannel 位置メソッド
FileChannel 内の特定の場所でデータの読み取り/書き込みが必要になる場合があります。 Position() メソッドを呼び出すことで、FileChannel の現在位置を取得できます。
また、position(long pos) メソッドを呼び出して、FileChannel の現在位置を設定することもできます。
以下に 2 つの例を示します。
次のようにコードをコピーします。
長い位置 = チャネル.位置();
チャンネル.位置(pos +123);
ファイルの終わりの後に位置を設定し、ファイル チャネルからデータを読み取ろうとすると、読み取りメソッドは -1 (ファイルの終わりフラグ) を返します。
ファイルの終端以降の位置を設定してチャンネルにデータを書き込むと、ファイルは現在の位置まで展開されてデータが書き込まれます。これにより、ディスク上の物理ファイルに書き込まれたデータ間のギャップである「ファイル ホール」が発生する可能性があります。
ファイルチャネルサイズメソッド
FileChannel インスタンスの size() メソッドは、インスタンスに関連付けられたファイルのサイズを返します。のように:
次のようにコードをコピーします。
長いファイルサイズ = チャネル.サイズ();
FileChannel の truncate メソッド
FileChannel.truncate() メソッドを使用してファイルをインターセプトできます。ファイルをインターセプトすると、ファイルの指定された長さ以降の部分が削除されます。のように:
次のようにコードをコピーします。
チャンネル.truncate(1024);
この例では、ファイルの最初の 1024 バイトをインターセプトします。
FileChannelのForceメソッド
FileChannel.force() メソッドは、まだディスクに書き込まれていないチャネル内のデータを強制的にディスクに書き込みます。パフォーマンス上の理由から、オペレーティング システムはデータをメモリにキャッシュするため、FileChannel に書き込まれたデータがすぐにディスクに書き込まれるという保証はありません。これを確実に行うには、force() メソッドを呼び出す必要があります。
Force() メソッドには、ファイルのメタデータ (アクセス許可情報など) を同時にディスクに書き込むかどうかを示すブール値パラメーターがあります。
次の例では、ファイル データとメタデータの両方をディスクに強制的に書き込みます。
次のようにコードをコピーします。
チャンネル.force(true);
ソケットチャネル
(このセクションの原文へのリンク、著者: Jakob Jenkov、翻訳者: Zheng Yuting、校正者: Ding Yi)
Java NIO の SocketChannel は、TCP ネットワーク ソケットに接続されたチャネルです。 SocketChannel は次の 2 つの方法で作成できます。
SocketChannel を開き、インターネット上のサーバーに接続します。
新しい接続が ServerSocketChannel に到着すると、SocketChannel が作成されます。
オープンソケットチャネル
SocketChannel を開く方法は次のとおりです。
次のようにコードをコピーします。
ソケットチャネルソケットチャネル = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
ソケットチャネルを閉じる
SocketChannel の使用が完了したら、SocketChannel.close() を呼び出して SocketChannel を閉じます。
次のようにコードをコピーします。
ソケットチャンネル.close();
SocketChannel からデータを読み取る
SocketChannel からデータを読み取るには、read() メソッドの 1 つを呼び出します。以下に例を示します。
次のようにコードをコピーします。
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead =socketChannel.read(buf);
まず、バッファを割り当てます。 SocketChannel から読み取られたデータは、このバッファーに配置されます。
次に、SocketChannel.read() を呼び出します。このメソッドは、SocketChannel から Buffer にデータを読み取ります。 read() メソッドによって返される int 値は、バッファに読み取られたバイト数を示します。 -1 が返された場合は、ストリームの終わりが読み取られた (接続が閉じられた) ことを意味します。
SocketChannel への書き込み
SocketChannel へのデータの書き込みでは、パラメーターとして Buffer を取る SocketChannel.write() メソッドが使用されます。例は次のとおりです。
次のようにコードをコピーします。
String newData = "ファイルに書き込む新しい文字列..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
チャネル書き込み(buf);
}
SocketChannel.write() メソッドは while ループ内で呼び出されることに注意してください。 Write() メソッドは、SocketChannel に書き込めるバイト数を保証できません。したがって、バッファに書き込むバイトがなくなるまで、 write() を繰り返し呼び出します。
ノンブロッキングモード
SocketChannel をノンブロッキング モードに設定すると、connect()、read()、および write() を非同期モードで呼び出すことができます。
接続する()
SocketChannel がノンブロッキング モードで、この時点で connect() が呼び出された場合、接続が確立される前にメソッドが戻る可能性があります。接続が確立されているかどうかを確認するには、finishConnect() メソッドを呼び出します。このような:
次のようにコードをコピーします。
ソケットチャネル.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(!socketChannel.finishConnect() ){
//待つか、別のことをしてください...
}
書く()
ノンブロッキング モードでは、write() メソッドは何かを書き込む前に戻ることがあります。したがって、ループ内で write() を呼び出す必要があります。以前にも例がありましたので、ここでは詳しく説明しません。
読む()
ノンブロッキング モードでは、データが読み取られる前に read() メソッドが返されることがあります。したがって、読み取られたバイト数を示す int 戻り値に注意する必要があります。
ノンブロッキングモードとセレクター
ノンブロッキング モードは、セレクターに 1 つ以上の SocketChannel を登録することで、どのチャネルが読み取り、書き込みなどの準備ができているかをセレクターに問い合わせることができます。 Selector と SocketChannel の組み合わせについては後で詳しく説明します。
サーバーソケットチャネル
(このセクションの原文へのリンク、著者: Jakob Jenkov、翻訳者: Zheng Yuting、校正者: Ding Yi)
Java NIO の ServerSocketChannel は、標準 IO の ServerSocket と同様に、新しい受信 TCP 接続をリッスンできるチャネルです。 ServerSocketChannel クラスは java.nio.channels パッケージにあります。
以下に例を示します。
次のようにコードをコピーします。
ServerSocketChannelserverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
ソケットチャネルソケットチャネル =
サーバーソケットチャネル.accept();
//socketChannel で何かを行う...
}
サーバーソケットチャネルを開く
ServerSocketChannel.open() メソッドを呼び出して、ServerSocketChannel を開きます。
次のようにコードをコピーします。
ServerSocketChannelserverSocketChannel = ServerSocketChannel.open();
サーバーソケットチャネルを閉じる
ServerSocketChannel.close() メソッドを呼び出して、ServerSocketChannel を閉じます。 次に例を示します。
次のようにコードをコピーします。
サーバーソケットチャネル.close();
新しい着信接続をリッスンする
ServerSocketChannel.accept() メソッドを通じて新しい受信接続をリッスンします。 accept() メソッドが戻ると、新しい受信接続を含む SocketChannel が返されます。したがって、accept() メソッドは、新しい接続が到着するまでブロックされます。
通常、1 つの接続をリッスンするだけではなく、次の例のように、while ループ内で accept() メソッドが呼び出されます。
次のようにコードをコピーします。
while(true){
ソケットチャネルソケットチャネル =
サーバーソケットチャネル.accept();
//socketChannel で何かを行う...
}
もちろん、while ループでは true 以外の他の終了基準を使用することもできます。
ノンブロッキングモード
ServerSocketChannel はノンブロッキング モードに設定できます。非ブロッキング モードでは、新しい受信接続がない場合、accept() メソッドはすぐに戻り値は null になります。 したがって、返された SocketChannel が null かどうかを確認する必要があります。のように:
次のようにコードをコピーします。
ServerSocketChannelserverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while(true){
ソケットチャネルソケットチャネル =
サーバーソケットチャネル.accept();
if(ソケットチャネル != null){
//socketChannel で何かを行う...
}
}
データグラムチャネル
(このセクションの原文へのリンク、著者: Jakob Jenkov、翻訳者: Zheng Yuting、校正者: Ding Yi)
Java NIO の DatagramChannel は、UDP パケットを送受信できるチャネルです。 UDP はコネクションレス型ネットワーク プロトコルであるため、他のチャネルのように読み書きすることはできません。データパケットを送受信します。
オープンデータグラムチャネル
DatagramChannel を開く方法は次のとおりです。
次のようにコードをコピーします。
DatagramChannel チャネル = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
この例で開かれた DatagramChannel は、UDP ポート 9999 でパケットを受信できます。
データを受信する
次のように、receive() メソッドを通じて DatagramChannel からデータを受信します。
次のようにコードをコピーします。
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
チャネル受信(buf);
accept() メソッドは、受信したデータ パケットの内容を指定されたバッファにコピーします。バッファが受信データを収容できない場合、超過したデータは破棄されます。
データを送信する
次のような send() メソッドを通じて DatagramChannel からデータを送信します。
次のようにコードをコピーします。
String newData = "ファイルに書き込む新しい文字列..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
この例では、文字列を「jenkov.com」サーバーの UDP ポート 80 に送信します。 サーバーはこのポートを監視していないため、何も起こりません。また、UDP にはデータ配信に関する保証がないため、送信パケットが受信されたかどうかは通知されません。
特定のアドレスに接続する
DatagramChannel は、ネットワーク内の特定のアドレスに「接続」できます。 UDP はコネクションレスであるため、特定のアドレスに接続しても、TCP チャネルのような実際の接続は作成されません。代わりに、DatagramChannel はロックされ、特定のアドレスからのみデータを送受信できるようになります。
以下に例を示します。
次のようにコードをコピーします。
channel.connect(new InetSocketAddress("jenkov.com", 80));
接続したら、従来のチャネルと同じように read() メソッドと write() メソッドを使用することもできます。データ転送に関する保証はありません。以下にいくつかの例を示します。
次のようにコードをコピーします。
int bytesRead = channel.read(buf);
int bytesWritten = channel.write(but);
パイプ
(このセクションの原文へのリンク、著者: Jakob Jenkov、翻訳者: Huang Zhong、校正者: Ding Yi)
Java NIO パイプは、2 つのスレッド間の一方向のデータ接続です。パイプにはソース チャネルとシンク チャネルがあります。データはシンク チャネルに書き込まれ、ソース チャネルから読み取られます。
以下にパイプの原理を示します。
パイプラインの作成
Pipe.open() メソッドを通じてパイプを開きます。例えば:
次のようにコードをコピーします。
パイプパイプ = Pipe.open();
パイプにデータを書き込む
パイプにデータを書き込むには、シンク チャネルにアクセスする必要があります。このような:
次のようにコードをコピーします。
Pipe.SinkChannel シンクチャンネル = Pipe.sink();
次のように、SinkChannel の write() メソッドを呼び出して、SinkChannel にデータを書き込みます。
次のようにコードをコピーします。
String newData = "ファイルに書き込む新しい文字列..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
<b>sinkChannel.write(buf);</b>
}
[コード]
パイプからデータを読み取る
パイプからデータを読み取るには、次のようにソース チャネルにアクセスする必要があります。
[コード]
Pipe.SourceChannel ソースチャネル = Pipe.source();
次のように、ソース チャネルの read() メソッドを呼び出してデータを読み取ります。
次のようにコードをコピーします。
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
read() メソッドによって返される int 値は、バッファに読み取られたバイト数を示します。