この記事では、主に Java I/O 操作と最適化に関する関連情報を詳しく紹介します。必要な方は参考にしてください。
概要:
ストリームは、開始点と終了点を持つ一連のバイトです。 . は、データ送信の一般用語または抽象化です。つまり、2 つのデバイス間のデータ伝送はストリームと呼ばれます。ストリームの本質はデータ伝送であり、より直感的なデータ操作を容易にするために、データ伝送の特性に応じてさまざまなカテゴリに抽象化されます。
Java I/O
I/O、Input/Output(入力/出力)の略称。 I/O に関する限り、概念的には 5 つのモデルがあります: ブロッキング I/O、ノンブロッキング I/O、I/O 多重化 (選択およびポーリング)、信号駆動型 I/O (SIGIO)、非同期 I/O ( POSIX aio_functions)。オペレーティング システムが異なると、上記のモデルのサポートも異なります。UNIX は IO 多重化をサポートします。システムが異なれば名前も異なります。FreeBSD では kqueue と呼ばれ、Linux では epoll と呼ばれます。 IOCP は、非同期 I/O をサポートするために Windows 2000 で誕生しました。
Java は、非同期 I/O をサポートするために、Java1.4 で導入された NIO1.0 が誕生し、各プラットフォームで異なる再利用方法が選択されます。 Linux は epoll を使用し、BSD は kqueue を使用し、Windows はオーバーラップ I/O を使用します。
Java I/O の関連メソッドは次のとおりです:
同期とブロッキング (I/O メソッド): サーバー実装モードは接続用のスレッドを開始し、各スレッドは独自に I/O を処理して待機します。 I/O は完了するまで、つまりクライアントが接続要求を行ったときに、サーバーは処理のためにスレッドを開始する必要があります。ただし、この接続が何も行わない場合、不要なスレッド オーバーヘッドが発生します。もちろん、この欠点はスレッド プール メカニズムによって改善できます。 I/O の制限は、I/O がストリーム指向、ブロッキング、シリアル プロセスであることです。各クライアントのソケット接続 I/O にはスレッドの処理が必要ですが、この期間中、このスレッドはソケットが閉じられるまで占有されます。この期間中、TCP 接続、データの読み取り、およびデータの返信はすべてブロックされます。言い換えれば、この期間中、スレッドによって占有されていた多くの CPU タイム スライスとメモリ リソースが無駄になりました。また、Socket 接続が確立されるたびに、Socket と個別に通信するための新しいスレッドが作成されます (ブロッキング通信を使用)。この方式は応答速度が速く、制御が容易です。接続数が少ない場合には非常に効果的ですが、接続ごとにスレッドを生成すると、間違いなくシステム リソースが無駄になります。
同期ノンブロッキング (NIO;メソッド): サーバー実装モードはリクエストに対してスレッドを開始し、各スレッドは I/O を個別に処理しますが、別のスレッドは I/O の完了を待たずにポーリングして I/O の準備ができているかどうかを確認します。クライアントによって送信された接続 すべてのリクエストはマルチプレクサーに登録され、マルチプレクサーは接続上に I/O リクエストがある場合にのみ処理用のスレッドを開始します。 NIO はバッファー指向、非ブロッキング、セレクターベースであり、スレッドを使用して複数のデータ送信チャネルをポーリングし、どのチャネルが処理されるかを監視します。 。サーバーはソケット接続リストを保存し、このリストをポーリングして、特定のソケット ポートに読み取られるデータがあることが判明した場合は、対応するソケット接続の読み取り操作を呼び出します。特定の Socket ポートに書き込む場合は、Socket 接続の対応する書き込み操作が呼び出されます。特定のポートの Socket 接続が中断された場合は、対応するデストラクター メソッドが呼び出されてポートを閉じます。これにより、サーバーのリソースを最大限に活用でき、効率が大幅に向上します。
非同期ノンブロッキング (AIO メソッド、JDK7 リリース): サーバー実装モードは有効なリクエストに対してスレッドを開始し、クライアントの I/O リクエストが最初に処理されます。完了後、サーバー アプリケーションは、処理のためにスレッドを開始するように通知されます。各スレッドは I/O を個別に処理する必要はなく、オペレーティング システムに委任するため、待つ必要はありません。 I/O が完了すると、オペレーティング システムから個別に通知されます。このモードは Linux の epoll モデルを使用します。
接続数が少ない場合は、従来の I/O モードの方が記述が簡単で、使用も簡単です。ただし、接続数が増加し続けると、従来の I/O 処理では接続ごとに 1 つのスレッドが必要になり、スレッド数が多くない場合は、スレッド数の増加に応じてプログラムの効率が向上します。一定の数は、スレッドの数が増加するにつれて減少します。したがって、従来のブロッキング I/O のボトルネックは、多すぎる接続を処理できないことです。ノンブロッキング I/O の目的は、このボトルネックを解決することです。たとえば、システムが 10,000 個の接続を処理する場合、ノンブロッキング I/O は 10,000 個のスレッドを開始する必要はありません。または 2,000 スレッドの処理。ノンブロッキング IO は接続を非同期に処理するため、接続がサーバーにリクエストを送信すると、サーバーは接続リクエストをリクエスト「イベント」として扱い、この「イベント」を対応する関数に割り当てて処理します。この処理関数をスレッドに入れて実行し、実行後にスレッドを返すことで、1 つのスレッドで複数のイベントを非同期に処理できます。ブロッキング I/O スレッドは、ほとんどの時間をリクエストの待機に費やします。
Java NIO
Java.nio パッケージは、バージョン 1.4 以降に Java に追加された新しいパッケージであり、特に I/O 操作の効率を向上させるために使用されます。
表 1 は、I/O と NIO の比較を示しています。
表 1. I/O VS NIO
I/O | NIO |
---|---|
ストリーム指向 | バッファ指向 |
ブロッキングIO | ノンブロッキングIO |
なし | セレクター |
NIO はブロックに基づいており、データをブロック単位で基本単位として処理します。 NIO では、2 つの最も重要なコンポーネントはバッファーとチャネルです。バッファは連続したメモリ ブロックであり、NIO がデータを読み書きするための転送場所です。チャネルは、バッファされたデータの送信元または宛先を識別し、バッファへのデータの読み取りまたは書き込みに使用され、バッファにアクセスするためのインターフェイスです。チャネルは双方向チャネルであり、読み取りまたは書き込みが可能です。ストリームは一方通行です。アプリケーションはチャネルを直接読み書きすることはできませんが、バッファを通じて行う必要があります。つまり、チャネルはバッファを通じてデータを読み書きします。
バッファを使用してデータを読み書きするには、通常次の 4 つの手順に従います。
バッファにデータを書き込む。
バッファからデータを読み取る。 () メソッドまたは Compact() メソッド。
バッファには多くの種類があり、バッファが異なれば、バッファ内のデータを操作するための異なる方法が提供されます。
バッファ データの書き込みには 2 つの状況があります:
put メソッドを直接呼び出して、そこにデータを書き込みます。
バッファからデータをチャネルに読み取る;
バッファからデータを読み取るには get() メソッドを使用します。
clear() メソッドと Compact() メソッド
clear()メソッドが呼び出されると、positionは0に戻り、limitはcapacityの値に設定されます。つまり、バッファがクリアされます。バッファ内のデータはクリアされませんが、これらのマークは、バッファへのデータの書き込みを開始する場所を示します。
バッファには、位置、容量、制限という 3 つの重要なパラメータがあります。
容量は、バッファの作成時に決定されるバッファのサイズを指します。
limit バッファが書き込みモードの場合は、書き込むことができるデータの量を指し、読み取りモードの場合は、読み込むことができるデータの量を指します。
position Buffer が書き込みモードの場合は、次に書き込まれるデータの位置を指します。読み取りモードの場合は、読み取られる現在のデータの位置を指します。データを読み書きするたびに、Position+1、つまり、Limit と Position は、Buffer を読み書きするときに異なる意味を持ちます。 Bufferのflipメソッドを呼び出して書き込みモードから読み取りモードに変更すると、リミット(読み取り) = 位置(書き込み)、位置(読み取り) = 0となります。 散在と集合
NIO は、分散と収集と呼ばれる構造化データを処理するためのメソッドを提供します。スキャッタリングとは、1 つだけではなく、一連のバッファにデータを読み取ることを指します。一方、集約はデータを一連のバッファーに書き込みます。分散と集約の基本的な使用法は、単一のバッファーを操作する場合の使用法と非常に似ています。スキャッタ読み取りでは、チャネルが各バッファを順番に埋めます。 1 つのバッファが満たされると、次のバッファが満たされ始めます。ある意味、バッファ配列は 1 つの大きなバッファのようなものです。ファイルの特定の構造がわかっている場合、各バッファのサイズがファイルの各セグメント構造のサイズと正確に一致するように、ファイル構造に準拠する複数のバッファを構築できます。このとき、スキャッタリーディングにより、対応する各バッファにコンテンツを一度に組み込むことができるため、操作が簡略化される。指定した形式でファイルを作成する必要がある場合は、最初に適切なサイズの Buffer オブジェクトを構築し、集約書き込みメソッドを使用してファイルを迅速に作成するだけです。リスト 1 では、例として FileChannel を使用して、分散と収集を使用して構造化ファイルの読み取りと書き込みを行う方法を示します。
リスト 1. スキャッタリングとギャザリングを使用した構造化ファイルの読み取りと書き込み
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class NIOScatteringandGathering { public void createFiles(String TPATH){ try { ByteBuffer bookBuf = ByteBuffer.wrap("java 性能优化技巧".getBytes("utf-8")); ByteBuffer autBuf = ByteBuffer.wrap("test".getBytes("utf-8")); int booklen = bookBuf.limit(); int autlen = autBuf.limit(); ByteBuffer[] bufs = new ByteBuffer[]{bookBuf,autBuf}; File file = new File(TPATH); if(!file.exists()){ try { file.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { FileOutputStream fos = new FileOutputStream(file); FileChannel fc = fos.getChannel(); fc.write(bufs); fos.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } ByteBuffer b1 = ByteBuffer.allocate(booklen); ByteBuffer b2 = ByteBuffer.allocate(autlen); ByteBuffer[] bufs1 = new ByteBuffer[]{b1,b2}; File file1 = new File(TPATH); try { FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); fc.read(bufs1); String bookname = new String(bufs1[0].array(),"utf-8"); String autname = new String(bufs1[1].array(),"utf-8"); System.out.println(bookname+" "+autname); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String[] args){ NIOScatteringandGathering nio = new NIOScatteringandGathering(); nio.createFiles("C://1.TXT"); } }
出力を以下のリスト 2 に示します。
リスト 2. 実行結果
java 性能优化技巧 test
リスト 3 に示すコードは、従来の I/O、バイトベースの NIO、およびメモリ マッピング ベースの NIO の 3 つの方法のパフォーマンスを比較しています。数万のデータを含むファイルの時間のかかる読み取りおよび書き込み操作が評価の基礎として使用されます。
リスト 3. I/O の 3 つの方法の比較テスト
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class NIOComparator { public void IOMethod(String TPATH){ long start = System.currentTimeMillis(); try { DataOutputStream dos = new DataOutputStream( new BufferedOutputStream(new FileOutputStream(new File(TPATH)))); for(int i=0;i<4000000;i++){ dos.writeInt(i);//写入 4000000 个整数 } if(dos!=null){ dos.close(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println(end - start); start = System.currentTimeMillis(); try { DataInputStream dis = new DataInputStream( new BufferedInputStream(new FileInputStream(new File(TPATH)))); for(int i=0;i<4000000;i++){ dis.readInt(); } if(dis!=null){ dis.close(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start); } public void ByteMethod(String TPATH){ long start = System.currentTimeMillis(); try { FileOutputStream fout = new FileOutputStream(new File(TPATH)); FileChannel fc = fout.getChannel();//得到文件通道 ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer for(int i=0;i<4000000;i++){ byteBuffer.put(int2byte(i));//将整数转为数组 } byteBuffer.flip();//准备写 fc.write(byteBuffer); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println(end - start); start = System.currentTimeMillis(); FileInputStream fin; try { fin = new FileInputStream(new File(TPATH)); FileChannel fc = fin.getChannel();//取得文件通道 ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer fc.read(byteBuffer);//读取文件数据 fc.close(); byteBuffer.flip();//准备读取数据 while(byteBuffer.hasRemaining()){ byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());//将 byte 转为整数 } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start); } public void mapMethod(String TPATH){ long start = System.currentTimeMillis(); //将文件直接映射到内存的方法 try { FileChannel fc = new RandomAccessFile(TPATH,"rw").getChannel(); IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, 4000000*4).asIntBuffer(); for(int i=0;i<4000000;i++){ ib.put(i); } if(fc!=null){ fc.close(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println(end - start); start = System.currentTimeMillis(); try { FileChannel fc = new FileInputStream(TPATH).getChannel(); MappedByteBuffer lib = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); lib.asIntBuffer(); while(lib.hasRemaining()){ lib.get(); } if(fc!=null){ fc.close(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start); } public static byte[] int2byte(int res){ byte[] targets = new byte[4]; targets[3] = (byte)(res & 0xff);//最低位 targets[2] = (byte)((res>>8)&0xff);//次低位 targets[1] = (byte)((res>>16)&0xff);//次高位 targets[0] = (byte)((res>>>24));//最高位,无符号右移 return targets; } public static int byte2int(byte b1,byte b2,byte b3,byte b4){ return ((b1 & 0xff)<<24)|((b2 & 0xff)<<16)|((b3 & 0xff)<<8)|(b4 & 0xff); } public static void main(String[] args){ NIOComparator nio = new NIOComparator(); nio.IOMethod("c://1.txt"); nio.ByteMethod("c://2.txt"); nio.ByteMethod("c://3.txt"); } }
リスト 3 実行中の出力をリスト 4 に示します。
リスト 4. 出力の実行
1139 906 296 157 234 125
上記の説明とリスト 3 に示すコードに加えて、NIO の Buffer には、システムの物理メモリに直接アクセスできるクラス DirectBuffer も提供されています。 DirectBuffer は ByteBuffer を継承していますが、通常の ByteBuffer とは異なります。通常の ByteBuffer は JVM ヒープ上にスペースを割り当てますが、その最大メモリは最大ヒープによって制限されますが、DirectBuffer は物理メモリ上に直接割り当てられ、ヒープ スペースを占有しません。通常の ByteBuffer にアクセスする場合、システムは常に間接操作に「カーネル バッファ」を使用します。 DirectrBuffer の場所がこの「カーネル バッファ」に相当します。したがって、DirectBuffer を使用する方が基盤システムに近い方法であるため、通常の ByteBuffer よりも高速です。 ByteBuffer と比較すると、DirectBuffer は読み取りおよび書き込みアクセス速度がはるかに高速ですが、DirectrBuffer の作成と破棄のコストは ByteBuffer よりも高くなります。 DirectBuffer と ByteBuffer を比較するコードをリスト 5 に示します。
リスト 5. DirectBuffer VS ByteBuffer
import java.nio.ByteBuffer; public class DirectBuffervsByteBuffer { public void DirectBufferPerform(){ long start = System.currentTimeMillis(); ByteBuffer bb = ByteBuffer.allocateDirect(500);//分配 DirectBuffer for(int i=0;i<100000;i++){ for(int j=0;j<99;j++){ bb.putInt(j); } bb.flip(); for(int j=0;j<99;j++){ bb.getInt(j); } } bb.clear(); long end = System.currentTimeMillis(); System.out.println(end-start); start = System.currentTimeMillis(); for(int i=0;i<20000;i++){ ByteBuffer b = ByteBuffer.allocateDirect(10000);//创建 DirectBuffer } end = System.currentTimeMillis(); System.out.println(end-start); } public void ByteBufferPerform(){ long start = System.currentTimeMillis(); ByteBuffer bb = ByteBuffer.allocate(500);//分配 DirectBuffer for(int i=0;i<100000;i++){ for(int j=0;j<99;j++){ bb.putInt(j); } bb.flip(); for(int j=0;j<99;j++){ bb.getInt(j); } } bb.clear(); long end = System.currentTimeMillis(); System.out.println(end-start); start = System.currentTimeMillis(); for(int i=0;i<20000;i++){ ByteBuffer b = ByteBuffer.allocate(10000);//创建 ByteBuffer } end = System.currentTimeMillis(); System.out.println(end-start); } public static void main(String[] args){ DirectBuffervsByteBuffer db = new DirectBuffervsByteBuffer(); db.ByteBufferPerform(); db.DirectBufferPerform(); } }
実行中の出力をリスト 6 に示します。
リスト 6. 出力の実行
920 110 531 390
リスト 6 からわかるように、DirectBuffer を頻繁に作成および破棄するコストは、ヒープ上にメモリ領域を割り当てるよりもはるかに大きくなります。パラメータ -XX:MaxDirectMemorySize=200M –Xmx200M を使用して、VM 引数の最大 DirectBuffer と最大ヒープ スペースを構成します。このコードでは、それぞれ 200M のスペースが要求されます。設定されたヒープ スペースが小さすぎる場合 (たとえば、1M)、エラーがスローされます。リスト 7 に示すように。
リスト 7. 実行エラー
Error occurred during initialization of VM Too small initial heap for new size specified
DirectBuffer 情報は GC に出力されません。GC はヒープ領域のメモリのリサイクルのみを記録するためです。 ByteBuffer はヒープ上に領域を割り当てるため、その GC 配列が比較的頻繁に行われることがわかります。Buffer を頻繁に作成する必要がある状況では、DirectBuffer を作成および破棄するコードは比較的高価であるため、DirectBuffer は使用すべきではありません。ただし、DirectBuffer を再利用できれば、システムのパフォーマンスを大幅に向上させることができます。リスト 8 は、DirectBuffer を監視するためのコードです。
リスト 8. DirectBuffer モニタリング コード
import java.lang.reflect.Field; public class monDirectBuffer { public static void main(String[] args){ try { Class c = Class.forName("java.nio.Bits");//通过反射取得私有数据 Field maxMemory = c.getDeclaredField("maxMemory"); maxMemory.setAccessible(true); Field reservedMemory = c.getDeclaredField("reservedMemory"); reservedMemory.setAccessible(true); synchronized(c){ Long maxMemoryValue = (Long)maxMemory.get(null); Long reservedMemoryValue = (Long)reservedMemory.get(null); System.out.println("maxMemoryValue="+maxMemoryValue); System.out.println("reservedMemoryValue="+reservedMemoryValue); } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
を実行した出力をリスト 9 に示します。
リスト 9. 出力を実行する
maxMemoryValue=67108864 reservedMemoryValue=0
NIO は使用が難しいため、多くの企業が JDK NIO をカプセル化する独自のフレームワーク (Apache の mina、JBoss の Netty、Sun の Grizzly など) を立ち上げています。フレームワークは直接Netty はトランスポート層の TCP または UDP プロトコルをカプセル化するだけであり、Web コンテナからの追加のサポートを必要としません。つまり、Web コンテナに限定されません。
Java AIO
AIO 関連のクラスとインターフェイス:
java.nio.channels.AsynchronousChannel:标记一个 Channel 支持异步 IO 操作; java.nio.channels.AsynchronousServerSocketChannel:ServerSocket 的 AIO 版本,创建 TCP 服务端,绑定地址,监听端口等; java.nio.channels.AsynchronousSocketChannel:面向流的异步 Socket Channel,表示一个连接; java.nio.channels.AsynchronousChannelGroup:异步 Channel 的分组管理,目的是为了资源共享。 一个 AsynchronousChannelGroup 绑定一个线程池,这个线程池执行两个任务:处理 IO 事件和派发 CompletionHandler。AsynchronousServerSocketChannel 创建的时候可以传入一个 AsynchronousChannelGroup,那么通过 AsynchronousServerSocketChannel 创建的 AsynchronousSocketChannel 将同属于一个组,共享资源; java.nio.channels.CompletionHandler:异步 IO 操作结果的回调接口,用于定义在 IO 操作完成后所作的回调工作。 AIO 的 API 允许两种方式来处理异步操作的结果:返回的 Future 模式或者注册 CompletionHandler,推荐用 CompletionHandler 的方式, 这些 handler 的调用是由 AsynchronousChannelGroup 的线程池派发的。这里线程池的大小是性能的关键因素。
ここでは、AIO がどのように動作するかを簡単に紹介するプログラム例を示します。
リスト 10. サーバー プログラム
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.ExecutionException; public class SimpleServer { public SimpleServer(int port) throws IOException { final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port)); //监听消息,收到后启动 Handle 处理模块 listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { public void completed(AsynchronousSocketChannel ch, Void att) { listener.accept(null, this);// 接受下一个连接 handle(ch);// 处理当前连接 } @Override public void failed(Throwable exc, Void attachment) { // TODO Auto-generated method stub } }); } public void handle(AsynchronousSocketChannel ch) { ByteBuffer byteBuffer = ByteBuffer.allocate(32);//开一个 Buffer try { ch.read(byteBuffer).get();//读取输入 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } byteBuffer.flip(); System.out.println(byteBuffer.get()); // Do something } }
リスト 11. クライアント プログラム
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; public class SimpleClientClass { private AsynchronousSocketChannel client; public SimpleClientClass(String host, int port) throws IOException, InterruptedException, ExecutionException { this.client = AsynchronousSocketChannel.open(); Future<?> future = client.connect(new InetSocketAddress(host, port)); future.get(); } public void write(byte b) { ByteBuffer byteBuffer = ByteBuffer.allocate(32); System.out.println("byteBuffer="+byteBuffer); byteBuffer.put(b);//向 buffer 写入读取到的字符 byteBuffer.flip(); System.out.println("byteBuffer="+byteBuffer); client.write(byteBuffer); } }
リスト 12. メイン関数
import java.io.IOException; import java.util.concurrent.ExecutionException; import org.junit.Test; public class AIODemoTest { @Test public void testServer() throws IOException, InterruptedException { SimpleServer server = new SimpleServer(9021); Thread.sleep(10000);//由于是异步操作,所以睡眠一定时间,以免程序很快结束 } @Test public void testClient() throws IOException, InterruptedException, ExecutionException { SimpleClientClass client = new SimpleClientClass("localhost", 9021); client.write((byte) 11); } public static void main(String[] args){ AIODemoTest demoTest = new AIODemoTest(); try { demoTest.testServer(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { demoTest.testClient(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
特別に詳細な記事がありますAIOのソースコード、設計思想、設計パターンなどを紹介します。
結論
I/O と NIO の重要な違いは、I/O を使用する場合、各接続で個別のスレッドが使用されることが多いのに対し、NIO ではそれぞれ 1 つのスレッドまたは少数のマルチスレッドのみが使用されることです。接続接続は 1 つのスレッドを共有します。 NIO のノンブロッキングの性質は継続的なポーリングを必要とし、システム リソースを消費するため、非同期ノンブロッキング モード AIO が誕生しました。この記事では、I/O、NIO、AIO の 3 つの入出力動作モードを 1 つずつ紹介し、簡単な説明と例を通して基本的な動作と最適化方法を習得できるように努めます。 上記は、Java I/O の操作と最適化を画像とテキストで詳しく紹介しています。さらに関連する内容については、PHP の中国語 Web サイト (www.php.cn) に注目してください。