背景知識
同期、非同期、ブロック、非ブロッキング
まず第一に、これらの概念は非常に簡単に混乱しますが、NIOに関与しているので、要約しましょう。
同期:API呼び出しが返されると、発信者は操作の結果を知っています(実際に読み取り/書き込み/書き込みのバイト数)。
非同期:同期と比較して、発信者はAPIコールが戻ったときの操作の結果を知りません。コールバックは後で結果に通知します。
ブロッキング:読み取るデータがない場合、またはすべてのデータを記述できない場合は、現在のスレッドが待機しています。
ノンブロッキング:読むときは、読み取ることができるデータと同じくらい読むことができます。書くときは、書くことができるデータと同じくらい書くことができます。
Oracleの公式ウェブサイトの文書によると、I/O運用の場合、同期と非同期部門の標準は「発信者がI/O操作が完了するのを待つ必要があるかどうか」です。この「I/O操作が完了するのを待つ」ことは、データを読み取る必要があるか、すべてのデータを記述する必要があることを意味するのではなく、TCP/IPプロトコルスタックバッファーとJVMバッファーの間にデータが送信される時間など、I/O操作が実際に実行されるときに発信者が待つ必要があるかどうかを意味します。
したがって、一般的に使用されるread()およびwrite()メソッドは同期I/oです。同期I/Oは、ブロッキングと非ブロッキングの2つのモードに分割されます。非ブロッキングモードの場合、読み取るデータがなく、I/O操作が実際に実行されていないことを検出すると、直接返されます。
要約すると、Javaには、実際には3つのメカニズムのみがあります:同期ブロッキングI/O、同期非ブロッキングI/O、および非同期I/O。以下で話しているのは最初の2つです。 JDK1.7は、Nio.2と呼ばれる非同期I/Oを導入し始めました。
伝統的なIO
新しいテクノロジーの出現には常に改善と改善が伴うことがわかっており、ジャワニオの出現も同様です。
従来のI/OはI/Oをブロックしており、主な問題はシステムリソースの無駄です。たとえば、TCP接続のデータを読み取るために、inputstreamのread()メソッドを呼び出します。スレッドは、データの到着期間中にメモリリソース(ストレージスレッドスタック)を占有しますが、何もしません。これは、うんちではなく穴を占領していることわざです。他の接続のデータを読むには、別のスレッドを起動する必要があります。これは、同時接続があまりない場合は問題ない場合がありますが、接続の数が特定のスケールに達すると、メモリリソースは多数のスレッドによって消費されます。一方、スレッドの切り替えには、プログラムカウンターやレジスタの値など、プロセッサのステータスを変更する必要があるため、多数のスレッド間の切り替えもリソースの無駄です。
テクノロジーの開発により、最新のオペレーティングシステムは、このリソースの無駄を回避できる新しいI/Oメカニズムを提供します。これに基づいて、Javanioが生まれ、NIOの代表的な特徴はI/Oを非ブロッキングしています。その後すぐに、非ブロッキングI/Oを使用するだけで問題を解決できないことがわかりました。なぜなら、非ブロッキングモードでは、データが読み取られないときにread()メソッドがすぐに返されるためです。データがいつ到着するかはわかりませんので、再試行するためにread()メソッドを呼び出し続けます。これは明らかにCPUリソースの無駄です。以下から、この問題を解決するためにセレクターコンポーネントが生まれたことがわかります。
Javanioコアコンポーネント
1.チャネル
コンセプト
JavanioのすべてのI/O操作は、ストリーム操作がストリームオブジェクトに基づいているのと同様に、チャネルオブジェクトに基づいているため、最初にチャネルとは何かを理解する必要があります。次のコンテンツは、JDK1.8のドキュメントから抜粋しています
Achannelは、1つのOrmory特徴的なi/ooperationで実行できるOroprogramコンポーネント、Annexity Suchasahardwaredevice、asasahardwaredeviceへの付属の接続を表します。
上記のコンテンツから、チャネルは特定のエンティティへの接続を表していることがわかります。これはファイル、ネットワークソケットなどです。つまり、チャンネルは、オペレーティングシステムの基礎となるI/Oサービスと対話するためのプログラムのためにJavanioが提供するブリッジです。
チャネルは非常に基本的で抽象的な説明であり、異なるI/Oサービスと対話し、異なるI/O操作を実行し、異なる実装を実装します。したがって、特定のものには、FileChannel、Socketchannelなどが含まれます。
チャネルは、使用するとストリームに似ています。データをバッファーに読み取るか、チャネルのバッファ内のデータを書き込むことができます。
もちろん、違いもあります。これは、主に次の2つのポイントに反映されています。
ストリームは一方通行である間に、チャネルを読み書きできます(inputstreamとoutputstreamに分割されます)
チャネルには非ブロッキングI/Oモードがあります
成し遂げる
Javanioで最も一般的に使用されるチャネル実装は次のとおりであり、従来のI/O操作クラスに1つずつ対応することがわかります。
FileChannel:ファイルの読み取りと書き込み
DatagramChannel:UDPプロトコルネットワーク通信
Socketchannel:TCPプロトコルネットワーク通信
Serversocketchannel:TCP接続を聞きます
2.バッファー
NIOで使用されるバッファーは、単純なバイト配列ではなく、カプセル化されたバッファークラスです。それが提供するAPIを介して、データを柔軟に操作できます。よく見てみましょう。
Java Basicタイプに対応して、NIOはBytebuffer、Charbuffer、Intbufferなどのさまざまなバッファタイプを提供します。違いは、バッファの単位長さが読み書きが異なることです(対応する型変数の単位で読み取りと書き込み)。
バッファには3つの非常に重要な変数があります。それらは、バッファーの作業メカニズム、すなわち
容量(総容量)
位置(ポインターの現在の位置)
制限(読み取り/書き込み境界位置)
バッファーの作業方法は、Cのキャラクター配列に非常に似ています。類推では、容量は配列の全長、位置は私たちが文字を読み取る/書き込むための添え字変数であり、制限はエンディング文字の位置です。バッファの先頭にある3つの変数の状況は次のとおりです
バッファーの読み取り/書き込みの過程で、位置は後方に移動し、制限は位置の動きの境界です。バッファーに書き込むとき、制限を容量のサイズに設定する必要があり、バッファーを読み取るときは、制限をデータの実際のエンド位置に設定する必要があることを想像することは難しくありません。 (注:バッファデータをチャネルに書き込むことはバッファー読み取り操作であり、チャンネルからバッファーまでのデータを読み取ることはバッファー書き込み操作です)
バッファで操作を読み取り/書き込む前に、主に次のように、位置と制限の値を正しく設定するために、バッファクラスによって提供されるいくつかの補助方法を呼び出すことができます
flip():位置の値に制限を設定し、バッファを読む前に位置を0に設定します。
Rewind():位置0を設定するだけです。通常、バッファデータを読み直す前に呼び出されます。たとえば、同じバッファのデータを読み取り、複数のチャネルに書き込むときに使用されます。
clear():初期状態に戻ります。つまり、制限は容量に等しく、位置は0に設定されます。書き込む前にバッファーを呼び出します。
compact():バッファーの先頭に未読データ(位置と制限の間のデータ)を移動し、このデータの最後に位置を次の位置に設定します。実際、このようなデータをバッファーに再び書き込むのと同等です。
次に、例を見て、FileChannelを使用してテキストファイルを読み書きし、この例を使用して、チャネルの読み取り可能な書き込み可能な特性とバッファーの基本的な使用法を確認します(FileChannelを非ブロッキングモードに設定できないことに注意してください)。
FileChannel Channel = new RandomAccessFile( "test.txt"、 "rw") world!/n ".getBytes(stardentcharsets.utf_8)); // buffer-> channelbytebuffer.flip(); charsetdecoder decoder = stardentcharsets.utf_8.newdecoder(); //すべてのデータbytebuffer.clear(); while(channel.read(bytebuffer)!= -1 || bytebuffer.position()> 0){bytebuffer.flip(); // decode charbuffer.clear(); decoder.decode(bytebuffer、charbuffer、false); system.out.out.out.print(charbuffer.flip(); tostring();データが残っている場合があります} channel.close();この例では、2つのバッファーが使用されます。ここでは、Bytebufferはチャンネルの読み取りと書き込みのデータバッファーであり、Charbufferはデコードされた文字を保存するために使用されます。 clear()とflip()の使用法は上記のとおりです。最後のcompact()メソッドは、charbufferのサイズがデコードされたデータbytebufferを収容するのに完全に十分であっても、このcompact()も不可欠であることに注意する必要があります。これは、一般的に使用される漢字のUTF-8エンコーディングが3バイトを占めるため、中央の切り捨てで発生する可能性が高いためです。以下の図をご覧ください。
デコーダーがバッファの端で0xe4を読み取ると、Unicodeにマッピングすることはできません。 decode()メソッドの3番目のパラメーターであるfalseは、デコーダーがマップ不能なバイトとその後のデータを追加データとして処理するために使用されます。したがって、ここではdecode()メソッドが停止し、位置は0xe4位置に戻ります。このようにして、「中程度」という単語によってエンコードされた最初のバイトはバッファーに残されており、正面にコンパクトにし、正しいシーケンスデータと一緒にスプライスする必要があります。文字エンコーディングについては、 「 ANSI、Unicode、BMP、UTF、その他のエンコード概念の説明」を参照できます。
ところで、この例のcharsetdecoderはJavanioの新機能でもあるため、少し発見する必要があります。 NIO操作はバッファ指向です(従来のI/Oはストリーム指向)。
この時点で、チャネルとバッファーの基本的な使用について学びました。次に、スレッドに複数のチャネルを管理できるようにする重要なコンポーネントについて説明します。
3.セレクター
セレクターとは何ですか
セレクターは、各チャネルの状態(またはイベント)を収集するために使用される特別なコンポーネントです。最初にチャンネルをセレクターに登録し、気にするイベントを設定します。次に、Select()メソッドを呼び出すことでイベントが発生するのを静かに待つことができます。
チャンネルには、次の4つのイベントがあります。
受け入れ:許容可能な接続があります
接続:正常に接続します
読む:読み取るデータがあります
書き込み:データを書くことができます
セレクターを使用する理由
上記のように、I/Oのブロックを使用する場合は、マルチスレッド(メモリの無駄)が必要であり、非ブロッキングI/Oを使用する場合は、常に再試行する必要があります(CPUの消費)。セレクターの出現は、この恥ずかしい問題を解決します。非ブロッキングモードでは、セレクターを介して、スレッドは準備が整ったチャネルでのみ機能し、盲目的に試す必要はありません。たとえば、すべてのチャネルでデータに到達しない場合、読み取りイベントは発生しません。また、スレッドはselect()メソッドで一時停止され、CPUリソースを放棄します。
使い方
以下に示すように、セレクターを作成し、チャネルを登録します。
注:チャネルをセレクターに登録するには、最初にチャネルを非ブロッキングモードに設定する必要があります。そうしないと、例外がスローされます。
selector selector = selector.open(); channel.configureblocking(false); selectionkey key = channel.register(selector、selectionkey.op_read);
レジスタ()メソッドの2番目のパラメーターは「関心セット」と呼ばれます。これは、心配しているイベントセットです。複数のイベントを気にする場合は、「BitalまたはOperator」で分離します。
selectionKey.op_read | selectionkey.op_write
この執筆方法はそれに不慣れではありません。ビット操作をサポートするプログラミング言語で再生されます。整数変数を使用すると、複数の状態を識別できます。それはどのように行われますか?実際には非常に簡単です。たとえば、最初にいくつかの定数を定義し、その値(バイナリ)は次のとおりです
1の値のビットはすべてずらしているため、ビットワイズまたは計算後に得られた値はあいまいさを持たず、どの変数から計算されるか逆に推定することができます。判断する方法、はい、それは「ビットと」操作です。たとえば、0011の状態セット変数値があります。「0011&op_read」の値が1または0であるかどうかを判断するだけで、セットにOP_READ状態が含まれているかどうかを判断する必要があります。
次に、レジスタ()メソッドは、この登録の情報を含むselectionKeyオブジェクトを返すことに注意してください。また、登録情報を変更することもできます。以下の完全な例から、select()後にselectionキーのコレクションを取得することにより、状態のあるチャネルが準備が整っていることもわかります。
完全な例
概念と理論的なことが説明されています(実際、ここに書いた後、私はあまり書いていないことがわかりました。完全な例を見てみましょう。
この例では、Javanioを使用して、単一の読み取りサーバーを実装しています。機能は非常に単純です。クライアント接続に耳を傾けます。接続が確立されると、クライアントのメッセージを読み取り、クライアントへのメッセージに応答します。
メッセージの最後を識別するために、文字 '/0'(値のバイト)を使用していることに注意してください。
単一のスレッドサーバー
public class nioServer {public static void main(string [] args)throws ioexception {// selectorselector selector = selector.open(); // tcp接続のリスニングチャネルserversocketchannel.open(); listenchannel.bind(new inetsocketAddress(9999)); listenchannel.configureblocking(false); // selectorに登録(その受け入れイベントを聞く)listenchannel.register(selector、selectionkey.op_accept); //バッファーbytebufferバッファー= bytebuffer.allocate(100); while(true){selector.select(); //イベントがリスニングされるまでブロックが発生するまでブロックされますiterator <selectkey> keyiter.selecturedKeys()。iterator(); //タートを介して選択されたチャネルイベントにアクセスします(keyiter.hasnext()){selectionkey keyiter.next() socketchannel channel =((serversocketchannel)key.channel())。 buffer.clear(); //ストリームの最後まで読み取り、TCP接続が切断されていることを示します。//したがって、読み取りイベントの聴取を聴くことが必要です//それ以外の場合、((socketchannel)key.channel())。 buffer.flip(); while(buffer.hasremaining()){byte b = buffer.get(); if(b == 0){// /0system.out.println()クライアントメッセージの最後に; {((socketchannel)key.channel())。write(buffer);}} else {system.out.print((char)b);}} //処理されたイベントについては、keyiter.remove();}}}}}}}}} //クライアント
このクライアントは、テストに純粋に使用されます。難しくなるためには、従来のライティング方法を使用し、コードは非常に短いです。
テストをより厳密にする必要がある場合は、サーバーの応答時間をカウントするために多数のクライアントを同時に実行する必要があり、接続が確立された直後にデータを送信しないでください。
パブリッククラスクライアント{public static void main(string [] args)スロー例外{socket socket = new socket( "localhost"、9999); inputstream is = socket.getinputStream(); outputStream os = socket.getOutputStream(); b; while((b = is.read()!= 0){system.out.print((char)b);} system.out.println(); socket.close();}}}要約します
上記は、JavaのNIOコアコンポーネントをすばやく理解することです。私はそれが誰にでも役立つことを願っています。興味のある友人は、このウェブサイトの他の関連する内容を引き続き参照できます。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!