首頁 > Java > java教程 > Java I/O 操作及優化圖文詳細介紹

Java I/O 操作及優化圖文詳細介紹

黄舟
發布: 2017-03-06 10:42:58
原創
1321 人瀏覽過

這篇文章主要介紹了Java I/O 操作及優化詳細介紹的相關資料,需要的朋友可以參考下

概要:

流是一組有順序的,有起點和終點的位元組集合,是資料傳輸的總稱或抽象。即資料在兩個裝置間的傳輸稱為流,流的本質是資料傳輸,根據資料傳輸特性將流抽象化為各種類,方便更直觀的進行資料操作。

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 方法):伺服器實作模式為一個連線啟動一個線程,每個執行緒親自處理I/O 並且一直等待I/O 直到完成,也就是客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理。但是如果這個連線不做任何事情就會造成不必要的執行緒開銷,當然可以透過執行緒池機制來改善這個缺點。 I/O 的限制是它是面向流的、阻塞式的、串列的一個過程。對每個客戶端的 Socket 連線 I/O 都需要一個執行緒來處理,而且在此期間,這個執行緒一直被佔用,直到 Socket 關閉。在這段期間,TCP 的連線、資料的讀取、資料的回傳都是被阻塞的。也就是說這段期間大量浪費了 CPU 的時間片和執行緒佔用的記憶體資源。此外,每建立一個 Socket 連線時,同時建立一個新執行緒對該 Socket 進行單獨通訊 (採用阻塞的方式通訊)。這種方式具有很快的反應速度,並且控制起來也很簡單。在連線數較少的時候非常有效,但是如果對每一個連線都產生一個執行緒無疑是對系統資源的一種浪費,如果連線數較多將會出現資源不足的情況;

#同步非阻塞(NIO 方法):伺服器實作模式為一個請求啟動一個線程,每個線程親自處理I/O,但是另外的線程輪詢檢查是否I/O 準備完畢,不必等待I/O 完成,即客戶端發送的連線請求都會註冊到多工器上,多工器輪詢到連線有I/O 請求時才啟動一個執行緒進行處理。 NIO 則是面向緩衝區,非阻塞式的,基於選擇器的,用一個線程來輪詢監控多個資料傳輸通道,哪個通道準備好了 (即有一組可以處理的資料) 就處理哪個通道。伺服器端保存一個Socket 連線列表,然後對這個列表進行輪詢,如果發現某個Socket 連接埠上有資料可讀時,則呼叫該Socket 連線的對應讀取動作;如果發現某個Socket 埠上有資料可寫時,則呼叫該Socket 連線的對應寫入操作;如果某個連接埠的Socket 連線已經中斷,則呼叫對應的析構方法關閉該連接埠。這樣能充分利用伺服器資源,效率得到大幅度提高;

異步非阻塞(AIO 方法,JDK7 發布):伺服器實作模式為一個有效請求啟動一個線程,客戶端的I/O 請求都是由作業系統先完成了再通知伺服器應用去啟動執行緒進行處理,每個執行緒不必親自處理I/O,而是委派作業系統來處理,也不需要等待I/O 完成,如果完成了操作系統會另行通知的。此模式採用了 Linux 的 epoll 模型。

在連線數不多的情況下,傳統 I/O 模式編寫較為容易,使用上較為簡單。但是隨著連線數的不斷增多,傳統I/O 處理每個連線都需要消耗一個線程,而程式的效率,當線程數不多時是隨著線程數的增加而增加,但是到一定的數量之後,是隨著線程數的增加而減少的。所以傳統阻塞式 I/O 的瓶頸在於不能處理過多的連接。非阻塞式 I/O 出現的目的就是為了解決這個瓶頸。非阻塞 IO 處理連接的線程數和連接數沒有聯繫,例如係統處理 10000 個連接,非阻塞 I/O 不需要啟動 10000 個線程,你可以用 1000 個,也可以用 2000 個線程來處理。因為非阻塞 IO 處理連接是異步的,當某個連接發送請求到伺服器,伺服器把這個連接請求當作一個請求“事件”,並把這個“事件”分配給相應的函數處理。我們可以把這個處理函數放到線程中去執行,執行完就把線程歸還,這樣一個線程就可以異步的處理多個事件。而阻塞式 I/O 的線程的大部分時間都被浪費在等待請求上了。

Java NIO

Java.nio 套件是 Java 在 1.4 版本之後新增加的套件,專門用來提高 I/O 操作的效率。

表 1 所示是 I/O 與 NIO 之間的對比內容。

表1. I/O VS NIO


#I/O # NIO
面向流 面向緩衝
阻塞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. 執行結果

java 性能优化技巧 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. 執行輸出

1139
906
296
157
234
125
登入後複製

#除上述描述及清單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. 運行輸出

920
110
531
390
登入後複製

由清單6 可知,頻繁建立和銷毀DirectBuffer 的代價遠大於在堆上分配記憶體空間。使用參數-XX:MaxDirectMemorySize=200M –Xmx200M 在VM Arguments 裡面配置最大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 等等,這些框架都直接封裝了傳輸層的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 等三種輸入輸出操作方式一一介紹,力求透過簡單的描述與實例讓讀者能夠掌握基本的操作、最佳化方法。

以上就是Java I/O 操作及優化圖文詳細介紹的內容,更多相關內容請關注PHP中文網(www.php.cn)!


來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板