- 概要
- Key Features
- 2.2. NIO – java.nio
- テスト サーバーを設定する
- Blocking IO – java.io
- 4.2. InputStream serverInput = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput));StringBuilder ourStore = new StringBuilder();
- Non-Blocking IO – java.nio
- 5.3. バッファからデータを格納する
- 結論
- Learn SpringコースでSpring 5とSpring Boot 2を始める:
概要
入力と出力を処理することは、Java プログラマにとって共通のタスクです。 このチュートリアルでは、オリジナルの java.io (IO) ライブラリと新しい java.nio (NIO) ライブラリを見て、ネットワークを介して通信するときにそれらがどのように異なるかを説明します。
Key Features
まず、両方のパッケージの主要な機能を見ていきましょう。 IO – java.io
java.io パッケージは Java 1.0 で導入され、Reader は Java 1.1 で導入されました。
- InputStream と OutputStream – 一度に1バイトずつデータを提供する
- Reader と Writer – ストリームの便利なラッパー
- blocking mode – メッセージ全体を待つ
2.2. NIO – java.nio
java.nio パッケージは Java 1.4 で導入され、Java 1.7 (NIO.2) でファイル操作と ASynchronousSocketChannel が強化され更新されたものである。 それは提供します。
- Buffer – データの塊を一度に読み込む
- CharsetDecoder – 生バイトから可読文字へのマッピング
- Channel – 外部との通信用
- Selector – SelectableChannel での多重化を有効にすることと、 I/O 準備ができた Channels へのアクセス
- non-unit(非同期)
では、サーバーにデータを送信したり、その応答を読み込むときに、これらのパッケージをそれぞれどのように使用するかを見てみましょう。
テスト サーバーを設定する
ここでは、WireMock を使用して、テストを独立して実行できるように、別のサーバーをシミュレートします。
WireMock の Maven 依存関係をテスト スコープに追加しましょう。
<dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.26.3</version> <scope>test</scope></dependency>
テスト クラスで、JUnit @Rule を定義して、空きポートで WireMock を起動しましょう。
@Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort());private String REQUESTED_RESOURCE = "/test.json";@Beforepublic void setup() { stubFor(get(urlEqualTo(REQUESTED_RESOURCE)) .willReturn(aResponse() .withStatus(200) .withBody("{ \"response\" : \"It worked!\" }")));}
これで、モック サーバーをセットアップしたので、テストを実行する準備ができました。
Blocking IO – java.io
Webサイトからデータを読み取り、オリジナルのブロッキング IO モデルがどのように機能するかを見てみましょう。 java.net.Socket を使用して、オペレーティング システムのポートの 1 つにアクセスします。 リクエストを送信する
この例では、リソースを取得するためにGETリクエストを作成します。
Socket socket = new Socket("localhost", wireMockRule.port())
通常のHTTPやHTTPS通信の場合、ポート番号は80または443になりますが、WireMockサーバーがリッスンしているポート番号にアクセスするためにソケットを作成します。
次に、ソケットでOutputStreamを開き、OutputStreamWriterでラップし、PrintWriterに渡してメッセージを書きましょう。 そして、要求が送信されるようにバッファをフラッシュすることを確認しましょう:
OutputStream clientOutput = socket.getOutputStream();PrintWriter writer = new PrintWriter(new OutputStreamWriter(clientOutput));writer.print("GET " + TEST_JSON + " HTTP/1.0\r\n\r\n");writer.flush();
4.2.
InputStream serverInput = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput));StringBuilder ourStore = new StringBuilder();
InputStream serverInput = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput));StringBuilder ourStore = new StringBuilder();
reader.readLine() を使用してブロックし、完全な行を待ち、その行をストアに追加します。 ストリームの終わりを示す null が得られるまで読み続けます。
for (String line; (line = reader.readLine()) != null;) { ourStore.append(line); ourStore.append(System.lineSeparator());}
Non-Blocking IO – java.nio
次に、同じ例で nio パッケージのノンブロッキング IO モデルがどのように機能するかを見てみましょう。
今回は、java.net.Socket の代わりに、サーバー上のポートにアクセスする java.nio.channel.SocketChannel を作成し、これに InetSocketAddress を渡します。
InetSocketAddress address = new InetSocketAddress("localhost", wireMockRule.port());SocketChannel socketChannel = SocketChannel.open(address);
次に、標準のUTF-8文字セットを取得して、メッセージをエンコードして書き込みます。
テキストを処理するので、生のバイト用の ByteBuffer と変換された文字用の CharBuffer (CharsetDecoder の補助) が必要です:
ByteBuffer byteBuffer = ByteBuffer.allocate(8192);CharsetDecoder charsetDecoder = charset.newDecoder();CharBuffer charBuffer = CharBuffer.allocate(8192);
データがマルチバイト文字セットで送られる場合、 CharBuffer にはスペースが残ります。
特に高速なパフォーマンスが必要な場合は、ByteBuffer.allocateDirect() を使用してネイティブ メモリで MappedByteBuffer を作成できることに留意してください。
バッファを扱う場合、バッファの大きさ (容量)、バッファ内の場所 (現在位置)、およびどこまで行けるか (限界) を知る必要があります。 SocketChannel からの読み込みは、ByteBuffer の現在の位置が次に書き込むバイトに設定され (最後に書き込んだバイトのすぐ後) 、その制限は変更されないまま終了します:
socketChannel.read(byteBuffer)
SocketChannel.read() は、バッファに書き込める、読み込んだバイトの数を返します。
まだすべてのデータを処理していないため、バッファに空き領域がない場合、SocketChannel.read()は読み込んだバイト数を返しますが、バッファの位置は0より大きいままです。
バッファの正しい位置から読み始めることを確認するために、Buffer.flip() を使用して ByteBuffer の現在の位置を 0 に、その上限を SocketChannel によって書き込まれた最後の 1 バイトに設定します。 次に、storeBufferContents メソッドを使用してバッファの内容を保存します(これについては後ほど説明します)。 最後に、buffer.compact() を使用してバッファを圧縮し、SocketChannel からの次の読み取りに備えて現在の位置を設定します。
データが部分的に届くかもしれないので、ソケットがまだ接続されているか、切断されたがまだバッファにデータが残っているかをチェックする終了条件を持つループで、バッファ読み込みコードをラップしましょう:
while (socketChannel.read(byteBuffer) != -1 || byteBuffer.position() > 0) { byteBuffer.flip(); storeBufferContents(byteBuffer, charBuffer, charsetDecoder, ourStore); byteBuffer.compact();}
そして、ソケットを閉じる() ことを忘れないでください (try-with-resources ブロックで開いた場合を除く):
socketChannel.close();
5.3. バッファからデータを格納する
サーバーからの応答にはヘッダーが含まれ、データ量がバッファのサイズを超えてしまう可能性があります。
メッセージを格納するために、まず、生のバイトを CharBuffer 内の文字にデコードします。 次に、文字データを読み取ることができるようにポインタを反転させ、拡張可能な StringBuilder に追加します。 最後に、次の書き込み/読み取りサイクルに備えて CharBuffer をクリアします。
では、バッファ、CharsetDecoder、および StringBuilder を渡す完全な storeBufferContents() メソッドを実装しましょう:
void storeBufferContents(ByteBuffer byteBuffer, CharBuffer charBuffer, CharsetDecoder charsetDecoder, StringBuilder ourStore) { charsetDecoder.decode(byteBuffer, charBuffer, true); charBuffer.flip(); ourStore.append(charBuffer); charBuffer.clear();}
結論
この記事では、オリジナルの java.
これに対して、java.nio ライブラリでは、Buffer と Channels を使用してノンブロッキング通信が可能で、メモリに直接アクセスして高速なパフォーマンスを提供することができます。
いつものように、この記事のコードはGitHubで公開されています。
Learn SpringコースでSpring 5とSpring Boot 2を始める:
>> コースをチェックアウトする