1. 程式人生 > >Java I/O 操作及優化建議

Java I/O 操作及優化建議

java.net 底層 str 面向對象 div 選擇 static 右移 linux

Java I/O

I/O。即 Input/Output(輸入/輸出) 的簡稱。

就 I/O 而言。概念上有 5 種模型:blocking I/O,nonblocking I/O。I/O multiplexing (select and poll),signal driven I/O (SIGIO)。asynchronous I/O (the POSIX aio_functions)。不同的操作系統對上述模型支持不同,UNIX 支持 IO 多路復用。不同系統叫法不同。freebsd 裏面叫 kqueue,Linux 叫 epoll。而 Windows2000 的時候就誕生了 IOCP 用以支持 asynchronous I/O。

Java 是一種跨平臺語言。為了支持異步 I/O,誕生了 NIO,Java1.4 引入的 NIO1.0 是基於 I/O 復用的,它在各個平臺上會選擇不同的復用方式。Linux 用的 epoll,BSD 上用 kqueue,Windows 上是重疊 I/O。

Java I/O 的相關方法例如以下所述:

  • 同步並堵塞 (I/O 方法):server實現模式為一個連接啟動一個線程,每一個線程親自處理 I/O 而且一直等待 I/O 直到完畢,即客戶端有連接請求時server端就須要啟動一個線程進行處理。可是假設這個連接不做不論什麽事情就會造成不必要的線程開銷。當然能夠通過線程池機制改善這個缺點。I/O 的局限是它是面向流的、堵塞式的、串行的一個過程。對每一個客戶端的 Socket 連接 I/O 都須要一個線程來處理,而且在此期間,這個線程一直被占用。直到 Socket 關閉。在這期間。TCP 的連接、數據的讀取、數據的返回都是被堵塞的。也就是說這期間大量浪費了 CPU 的時間片和線程占用的內存資源。此外。每建立一個 Socket 連接時。同一時候創建一個新線程對該 Socket 進行單獨通信 (採用堵塞的方式通信)。

    這樣的方式具有非常快的響應速度,而且控制起來也非常easy。

    在連接數較少的時候非常有效。可是假設對每一個連接都產生一個線程無疑是對系統資源的一種浪費,假設連接數較多將會出現資源不足的情況;

  • 同步非堵塞 (NIO 方法):server實現模式為一個請求啟動一個線程。每一個線程親自處理 I/O,可是另外的線程輪詢檢查是否 I/O 準備完畢。不必等待 I/O 完畢,即客戶端發送的連接請求都會註冊到多路復用器上,多路復用器輪詢到連接有 I/O 請求時才啟動一個線程進行處理。NIO 則是面向緩沖區,非堵塞式的。基於選擇器的,用一個線程來輪詢監控多個傳輸數據通道,哪個通道準備好了 (即有一組能夠處理的數據) 就處理哪個通道。server端保存一個 Socket 連接列表,然後對這個列表進行輪詢。假設發現某個 Socket 端口上有數據可讀時,則調用該 Socket 連接的對應讀操作;假設發現某個 Socket 端口上有數據可寫時。則調用該 Socket 連接的對應寫操作。假設某個端口的 Socket 連接已經中斷,則調用對應的析構方法關閉該端口。

    這樣能充分利用server資源,效率得到大幅度提高;

  • 異步非堵塞 (AIO 方法,JDK7 公布):server實現模式為一個有效請求啟動一個線程,客戶端的 I/O 請求都是由操作系統先完畢了再通知server應用去啟動線程進行處理。每一個線程不必親自處理 I/O,而是委派操作系統來處理,而且也不須要等待 I/O 完畢,假設完畢了操作系統會另行通知的。該模式採用了 Linux 的 epoll 模型。

在連接數不多的情況下,傳統 I/O 模式編寫較為easy。使用上也較為簡單。

可是隨著連接數的不斷增多,傳統 I/O 處理每一個連接都須要消耗一個線程。而程序的效率,當線程數不多時是隨著線程數的添加而添加,可是到一定的數量之後。是隨著線程數的添加而降低的。所以傳統堵塞式 I/O 的瓶頸在於不能處理過多的連接。非堵塞式 I/O 出現的目的就是為了解決這個瓶頸。

非堵塞 IO 處理連接的線程數和連接數沒有聯系。比如系統處理 10000 個連接,非堵塞 I/O 不須要啟動 10000 個線程,你能夠用 1000 個,也能夠用 2000 個線程來處理。因為非堵塞 IO 處理連接是異步的,當某個連接發送請求到server,server把這個連接請求當作一個請求“事件”。並把這個“事件”分配給對應的函數處理。我們能夠把這個處理函數放到線程中去執行,執行完就把線程歸還,這樣一個線程就能夠異步的處理多個事件。而堵塞式 I/O 的線程的大部分時間都被浪費在等待請求上了。

Java NIO

Java.nio 包是 Java 在 1.4 版本號之後新添加的包。專門用來提高 I/O 操作的效率。

表 1 所看到的是 I/O 與 NIO 之間的對照內容。

表 1. I/O VS NIO

I/ONIO
面向流面向緩沖
堵塞 IO非堵塞 IO
選擇器

NIO 是基於塊 (Block) 的,它以塊為基本單位處理數據。

在 NIO 中,最為重要的兩個組件是緩沖 Buffer 和通道 Channel。緩沖是一塊連續的內存塊。是 NIO 讀寫數據的中轉地。

通道標識緩沖數據的源頭或者目的地,它用於向緩沖讀取或者寫入數據,是訪問緩沖的接口。Channel 是一個雙向通道,就可以讀,也可寫。Stream 是單向的。應用程序不能直接對 Channel 進行讀寫操作,而必須通過 Buffer 來進行。即 Channel 是通過 Buffer 來讀寫數據的。

使用 Buffer 讀寫數據一般遵循下面四個步驟:

  1. 寫入數據到 Buffer。
  2. 調用 flip() 方法。
  3. 從 Buffer 中讀取數據。
  4. 調用 clear() 方法或者 compact() 方法。

當向 Buffer 寫入數據時,Buffer 會記錄下寫了多少數據。一旦要讀取數據,須要通過 flip() 方法將 Buffer 從寫模式切換到讀模式。在讀模式下。能夠讀取之前寫入到 Buffer 的全部數據。

一旦讀完了全部的數據,就須要清空緩沖區。讓它能夠再次被寫入。有兩種方式能清空緩沖區:調用 clear() 或 compact() 方法。clear() 方法會清空整個緩沖區。compact() 方法僅僅會清除已經讀過的數據。不論什麽未讀的數據都被移到緩沖區的起始處。新寫入的數據將放到緩沖區未讀數據的後面。

Buffer 有多種類型,不同的 Buffer 提供不同的方式操作 Buffer 中的數據。

圖 1 Buffer 接口層次圖

技術分享

Buffer 寫數據有兩種情況:

  1. 從 Channel 寫到 Buffer,如樣例中 Channel 從文件裏讀取數據,寫到 Channel;
  2. 直接調用 put 方法,往裏面寫數據。

從 Buffer 中讀取數據有兩種方式:

  1. 從 Buffer 讀取數據到 Channel;
  2. 使用 get() 方法從 Buffer 中讀取數據。

Buffer 的 rewin 方法將 position 設回 0,所以你能夠重讀 Buffer 中的全部數據。

limit 保持不變,仍然表示能從 Buffer 中讀取多少個元素(byte、char 等)。

clear() 和 compact() 方法

一旦讀完 Buffer 中的數據,須要讓 Buffer 準備好再次被寫入。能夠通過 clear() 或 compact() 方法來完畢。

假設調用的是 clear() 方法,position 將被設回 0,limit 被設置成 capacity 的值。

換句話說。Buffer 被清空了。Buffer 中的數據並未清除。僅僅是這些標記告訴我們能夠從哪裏開始往 Buffer 裏寫數據。

假設 Buffer 中有一些未讀的數據,調用 clear() 方法。數據將“被遺忘”,意味著不再有不論什麽標記會告訴你哪些數據被讀過,哪些還沒有。假設 Buffer 中仍有未讀的數據,且興許還須要這些數據,可是此時想要先寫些數據,那麽使用 compact() 方法。

compact() 方法將全部未讀的數據復制到 Buffer 起始處。然後將 position 設到最後一個未讀元素正後面。

limit 屬性依舊像 clear() 方法一樣,設置成 capacity。如今 Buffer 準備好寫數據了,可是不會覆蓋未讀的數據。

Buffer 參數

Buffer 有 3 個重要的參數:位置 (position)、容量 (capacity) 和上限 (limit)。

capacity 是指 Buffer 的大小。在 Buffer 建立的時候已經確定。

limit 當 Buffer 處於寫模式,指還能夠寫入多少數據。處於讀模式,指還有多少數據能夠讀。

position 當 Buffer 處於寫模式,指下一個寫數據的位置;處於讀模式,當前將要讀取的數據的位置。

每讀寫一個數據,position+1,也就是 limit 和 position 在 Buffer 的讀/寫時的含義不一樣。

當調用 Buffer 的 flip 方法。由寫模式變為讀模式時,limit(讀)=position(寫),position(讀) =0。

散射&聚集

NIO 提供了處理結構化數據的方法,稱之為散射 (Scattering) 和聚集 (Gathering)。散射是指將數據讀入一組 Buffer 中,而不僅僅是一個。

聚集與之相反,指將數據寫入一組 Buffer 中。散射和聚集的基本用法和對單個 Buffer 操作時的用法相當相似。在散射讀取中,通道依次填充每一個緩沖區。填滿一個緩沖區後,它就開始填充下一個,在某種意義上,緩沖區數組就像一個大緩沖區。在已知文件詳細結構的情況下,能夠構造若幹個符合文件結構的 Buffer,使得各個 Buffer 的大小恰好符合文件各段結構的大小。此時。通過散射讀的方式能夠一次將內容裝配到各個對應的 Buffer 中。從而簡化操作。

假設須要創建指定格式的文件,僅僅要先構造好大小合適的 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. 執行結果
1java 性能優化技巧 test

清單 3 所看到的代碼對傳統 I/O、基於 Byte 的 NIO、基於內存映射的 NIO 三種方式進行了性能上的對照,使用一個有 400 萬數據的文件的讀、寫操作耗時作為評測根據。

清單 3. I/O 的三種方式對照試驗
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. 執行輸出
1139906296157234125

除上述描寫敘述及清單 3 所看到的代碼以外,NIO 的 Buffer 還提供了一個能夠直接訪問系統物理內存的類 DirectBuffer。DirectBuffer 繼承自 ByteBuffer,但和普通的 ByteBuffer 不同。普通的 ByteBuffer 仍然在 JVM 堆上分配空間。其最大內存受到最大堆的限制。而 DirectBuffer 直接分配在物理內存上,並不占用堆空間。

在對普通的 ByteBuffer 訪問時,系統總是會使用一個“內核緩沖區”進行間接的操作。

而 DirectrBuffer 所處的位置,相當於這個“內核緩沖區”。因此,使用 DirectBuffer 是一種更加接近系統底層的方法,所以。它的速度比普通的 ByteBuffer 更快。

DirectBuffer 相對於 ByteBuffer 而言。讀寫訪問速度快非常多,可是創建和銷毀 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. 執行輸出
920110531390

由清單 6 可知,頻繁創建和銷毀 DirectBuffer 的代價遠遠大於在堆上分配內存空間。使用參數-XX:MaxDirectMemorySize=200M –Xmx200M 在 VM Arguments 裏面配置最大 DirectBuffer 和最大堆空間,代碼中分別請求了 200M 的空間。假設設置的堆空間過小,比如設置 1M。會拋出錯誤如清單 7 所看到的。

清單 7. 執行錯誤
Error occurred during initialization of VMToo 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. 執行輸出
12maxMemoryValue=67108864reservedMemoryValue=0

因為 NIO 使用起來較為困難,所以很多公司推出了自己封裝 JDK NIO 的框架,比如 Apache 的 Mina,JBoss 的 Netty。Sun 的 Grizzly 等等,這些框架都直接封裝了傳輸層的 TCP 或 UDP 協議,當中 Netty 僅僅是一個 NIO 框架,它不須要 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.Main 函數

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 則是使用單線程或者僅僅使用少量的多線程。每一個連接共用一個線程。

而因為 NIO 的非堵塞須要一直輪詢,比較消耗系統資源,所以異步非堵塞模式 AIO 就誕生了。

本文對 I/O、NIO、AIO 等三種輸入輸出操作方式進行一一介紹,力求通過簡單的描寫敘述和實例讓讀者能夠掌握主要的操作、優化方法。

本文原文地址:http://www.importnew.com/16481.html


Java I/O 操作及優化建議