Java IOとJava NIOの主な違い

Javaで標準の入力/出力の研究を始めたとき、最初は特定の例外の全リストで補足されたjava.io. *パッケージの豊富なインターフェイスとクラスに少しショックを受けました。



インターネットからさまざまなチュートリアルを勉強して実装するのにかなりの時間を費やした後、私は自信を持ち始め、安reliefのため息をつきました。 しかし、ある時点で、 java.nio。*パッケージ(Java NIOまたはJava New IOとしても知られている)があるため、すべてが始まったばかりであることに気付きました。 最初は、側面図のように、同じもののように見えました。 しかし、判明したように、仕事の原則と彼らの助けを借りて解決されたタスクの両方に大きな違いがあります。



Jakob Jenkovによる記事「Java NIO vs. IO 以下に、適応された形式で示します。



この記事はJava IOおよびJava NIOの使用に関するガイドではないことに注意してください。 その目標は、Javaを学び始めた人々に、I / Oを編成するための2つの指定されたツールの概念的な違いを理解する機会を提供することです。



Java IOとJava NIOの主な違い



IO NIO
ストリーム指向 バッファー指向
ブロッキング(同期)入力/出力 ノンブロッキング(非同期)I / O
セレクター


ストリーム指向およびバッファ指向の入出力



I / Oを編成する2つのアプローチの主な違いは、Java IOはストリーム指向であり、Java NIOはバッファ指向であることです。 さらに詳細に分析します。



ストリーム指向のI / Oには、時間単位ごとに1バイト以上のストリームを順番に読み書きすることが含まれます。 この情報はどこにもキャッシュされません。 したがって、データストリームを前方または後方に任意に移動することはできません。 このような操作を実行する場合は、最初にデータをバッファーにキャッシュする必要があります。



ストリーム指向入力:







ストリーム指向の出力:







Java NIOのベースとなるアプローチはわずかに異なります。 データはバッファに読み込まれ、さらに処理されます。 バッファ内を前後に移動できます。 これにより、データ処理の柔軟性が少し向上します。 同時に、バッファに正しい処理に必要なデータ量が含まれているかどうかを確認する必要があります。 また、バッファにデータを読み込むときに、バッファでまだ処理されていないデータを破壊しないようにする必要があります。



ブロッキングおよびノンブロッキング入出力



Java IOのI / Oストリームがブロックされています。 これは、java.io。*パッケージの任意のクラスのread()またはwrite()メソッドがtreadスレッドで呼び出されると、データが読み書きされるまでロックが発生することを意味します。 現時点の実行スレッドは他に何もできません。



ノンブロッキングJava NIOモードを使用すると、チャネルからの読み取りデータを要求し、現在利用可能なデータのみを取得できます。利用可能なデータがない場合は何も取得できません。 データが読み取り可能になるまでロックされたままになる代わりに、実行スレッドは別のことを行う場合があります。



チャンネル
チャネルは、データが入出力される論理(非物理)ポータルであり、バッファーはこれらの送信データのソースまたはレシーバーです。 出力を整理するとき、送信するデータはバッファに配置され、チャネルに送信されます。 入力すると、チャネルからのデータは指定したバッファに配置されます。



チャネルは、バイトバッファとチャネルの反対側のエンティティ間でデータを効率的に転送するパイプラインに似ています。 チャネルは、最小限のオーバーヘッドでオペレーティングシステムのI / Oサービスにアクセスできるゲートウェイであり、バッファは、データの送受信に使用されるこれらのゲートウェイの内部エンドポイントです。



同じことは、非ブロッキング出力にも当てはまります。 実行スレッドは、チャネルへのデータの書き込みを要求できますが、完全に書き込まれるまで待機することはできません。



したがって、Java NIOの非ブロックモードでは、ロック状態で待機する空の書き込み時間の代わりに、1つの実行スレッドを使用して複数のタスクを解決できます。 最も一般的な方法は、ワークフローの保存されたランタイムを使用して、別のチャネルまたは他のチャネルでI / O操作を処理することです。



セレクター



Java NIOのセレクターを使用すると、単一の実行スレッドで複数の入力チャネルを監視できます。 複数のチャネルをセレクターに登録し、実行の単一スレッドを使用して、処理に使用できるデータがあるチャネルを処理したり、記録の準備ができているチャネルを選択したりできます。



セレクターを使用することの概念と利点をよりよく理解するために、プログラミングを無視して、駅を想像してみましょう。 セレクタなしのオプション:3つの線路(チャネル)があり、列車(バッファからのデータ)はいつでも各線路に到着できます。駅の従業員(実行フロー)は各線路で常に待機しており、到着した列車にサービスを提供します。 その結果、たとえ電車がまったくなくても、3人の従業員が駅に常にいます。 セレクターを使用したオプション:状況は同じですが、プラットフォームごとに、列車の到着について駅の従業員に信号を送るインジケータ(実行のフロー)があります。 したがって、駅には1人の従業員がいるだけで十分です。



Java NIOとJava IOのアプリケーション設計への影響



Java NIOとJava IOを選択すると、アプリケーションの次の設計側面に影響を与える可能性があります。

1.入力/出力クラスへのAPI呼び出し。

2.データ処理;

3.データの処理に使用される実行スレッドの数。



I / OクラスアクセスAPI



当然、Java NIOの使用はJava IOの使用とは大きく異なります。 たとえば、InputStreamを使用してバイト単位でデータを読み取るのではなく、データを最初にバッファに読み取って、そこから処理する必要があります。



データ処理



Java NIOを使用する場合のデータ処理も異なります。

すでに述べたように、Java IOを使用するときは、InputStreamまたはReaderでバイト単位でデータを読み取ります。 テキスト情報の行を読むことを想像してください:



名前:アンナ

年齢:25

メール:anna@mailserver.com

電話番号:1234567890



このテキスト行のストリームは、次のように処理できます。



InputStream input = ... ; BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String nameLine = reader.readLine(); String ageLine = reader.readLine(); String emailLine = reader.readLine(); String phoneLine = reader.readLine();
      
      





処理プロセスの状態は、プログラムの進行状況に依存することに注意してください。 最初のreadLine()メソッドが実行結果を返すとき、テキストの行全体が読み取られたことを確認します。 メソッドはブロックされており、ブロック全体の行が読み取られるまでブロックアクションが続行されます。 また、この行に名前が含まれていることも明確に理解できます。 同様に、メソッドが2回目に呼び出されると、結果として年齢を取得することがわかります。



ご覧のとおり、プログラム実行の進捗は、新しいデータが読み取り可能になったときにのみ達成され、各ステップでどのようなデータであるかがわかります。 進行状況のスレッドがデータの特定の部分の読み取りを進行すると、入力ストリームは(ほとんどの場合)データを逆方向に移動しなくなります。 この原則は、次のスキームで十分に実証されています。







Java IOを使用した実装は少し異なります。



 ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer);
      
      





ByteBufferのチャネルからバイトが読み取られる2行目のコードに注意してください。 このメソッドの実行結果が返されるとき、必要なデータがすべてバッファ内にあることを確認することはできません。 知っているのは、バッファにいくつかのバイトが含まれていることだけです。 これにより、処理が少し複雑になります。



読み取り(バッファ)メソッドの最初の呼び出しの後、半分の行のみがバッファに読み取られたと想像してください。 たとえば、「名前:An」。 そのようなデータを処理できますか? おそらくない。 少なくとも1行のテキストがバッファに読み込まれるまで待つ必要があります。



それでは、バッファを正しく処理するのに十分なデータがあるかどうかをどのようにして知るのでしょうか? しかし、まさか。 見つける唯一の方法は、バッファ内に含まれるデータを調べることです。 その結果、正しい処理に使用できるようになるまで、バッファ内のデータを数回チェックする必要があります。 これは非効率的で、プログラムの設計に悪影響を与える可能性があります。 例:



 ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { bytesRead = inChannel.read(buffer); }
      
      







bufferFull()メソッドは、バッファに読み込まれたデータ量を監視し、バッファがいっぱいかどうかに応じてtrueまたはfalseを返す必要があります。 つまり、バッファの処理準備ができている場合、バッファはいっぱいであると見なされます。



また、bufferFull()メソッドは、バッファを変更しないままにしておく必要があります。そうしないと、読み込まれたデータの次の部分が間違った場所に書き込まれる可能性があります。



バッファがいっぱいの場合、そこからのデータを処理できます。 それが満たされていなくても、それが特定のケースで理にかなっている場合、あなたはまだその中に既にあるデータを処理することができます。 ほとんどの場合、これは無意味です。



次の図は、正しい処理のためにバッファ内のデータの準備状況を判断するプロセスを示しています。







まとめ



Java NIOでは、最小数のスレッドを使用して複数のチャネル(ネットワーク接続またはファイル)を管理できます。 ただし、このアプローチの価格は、ブロッキングストリーム、データ解析を使用するよりも複雑です。



数千のオープン接続を同時に管理する必要があり、それぞれが少量のデータのみを転送する場合、アプリケーションにJava NIOを選択すると利点が得られます。 このタイプの設計は、次の図に概略的に示されています。







大量のデータが送信される接続が少ない場合、I / Oシステムの古典的な設計が最良の選択です。







Java NIOは決してJava IOの代替ではないことを理解することが重要です。 これは改善とみなすべきです-入力/出力を整理する機能を大幅に拡張できるツールです。 両方のアプローチの力を適切に使用すると、優れた高性能システムを構築できます。



Java 1.7のリリースに伴い、Java NIO.2も登場しましたが、その固有のイノベーションは、まずファイルI / Oに関連するため、この記事の範囲外です。



PS:Nino Guarnacciによる非常に価値のある記事の資料- 「Java.nio vs Java.io」もこの投稿で使用されています



All Articles