概要:
成都創(chuàng)新互聯(lián)是專業(yè)的濰城網(wǎng)站建設(shè)公司,濰城接單;提供成都網(wǎng)站建設(shè)、做網(wǎng)站,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行濰城網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
流是一組有順序的,有起點和終點的字節(jié)集合,是對數(shù)據(jù)傳輸?shù)目偡Q或抽象。即數(shù)據(jù)在兩設(shè)備間的傳輸稱為流,流的本質(zhì)是數(shù)據(jù)傳輸,根據(jù)數(shù)據(jù)傳輸特性將流抽象為各種類,方便更直觀的進行數(shù)據(jù)操作。
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)。不同的操作系統(tǒng)對上述模型支持不同,UNIX 支持 IO 多路復(fù)用。不同系統(tǒng)叫法不同,freebsd 里面叫 kqueue,Linux 叫 epoll。而 Windows2000 的時候就誕生了 IOCP 用以支持 asynchronous I/O。
Java 是一種跨平臺語言,為了支持異步 I/O,誕生了 NIO,Java1.4 引入的 NIO1.0 是基于 I/O 復(fù)用的,它在各個平臺上會選擇不同的復(fù)用方式。Linux 用的 epoll,BSD 上用 kqueue,Windows 上是重疊 I/O。
Java I/O 的相關(guān)方法如下所述:
同步并阻塞 (I/O 方法):服務(wù)器實現(xiàn)模式為一個連接啟動一個線程,每個線程親自處理 I/O 并且一直等待 I/O 直到完成,即客戶端有連接請求時服務(wù)器端就需要啟動一個線程進行處理。但是如果這個連接不做任何事情就會造成不必要的線程開銷,當然可以通過線程池機制改善這個缺點。I/O 的局限是它是面向流的、阻塞式的、串行的一個過程。對每一個客戶端的 Socket 連接 I/O 都需要一個線程來處理,而且在此期間,這個線程一直被占用,直到 Socket 關(guān)閉。在這期間,TCP 的連接、數(shù)據(jù)的讀取、數(shù)據(jù)的返回都是被阻塞的。也就是說這期間大量浪費了 CPU 的時間片和線程占用的內(nèi)存資源。此外,每建立一個 Socket 連接時,同時創(chuàng)建一個新線程對該 Socket 進行單獨通信 (采用阻塞的方式通信)。這種方式具有很快的響應(yīng)速度,并且控制起來也很簡單。在連接數(shù)較少的時候非常有效,但是如果對每一個連接都產(chǎn)生一個線程無疑是對系統(tǒng)資源的一種浪費,如果連接數(shù)較多將會出現(xiàn)資源不足的情況;
同步非阻塞 (NIO 方法):服務(wù)器實現(xiàn)模式為一個請求啟動一個線程,每個線程親自處理 I/O,但是另外的線程輪詢檢查是否 I/O 準備完畢,不必等待 I/O 完成,即客戶端發(fā)送的連接請求都會注冊到多路復(fù)用器上,多路復(fù)用器輪詢到連接有 I/O 請求時才啟動一個線程進行處理。NIO 則是面向緩沖區(qū),非阻塞式的,基于選擇器的,用一個線程來輪詢監(jiān)控多個數(shù)據(jù)傳輸通道,哪個通道準備好了 (即有一組可以處理的數(shù)據(jù)) 就處理哪個通道。服務(wù)器端保存一個 Socket 連接列表,然后對這個列表進行輪詢,如果發(fā)現(xiàn)某個 Socket 端口上有數(shù)據(jù)可讀時,則調(diào)用該 Socket 連接的相應(yīng)讀操作;如果發(fā)現(xiàn)某個 Socket 端口上有數(shù)據(jù)可寫時,則調(diào)用該 Socket 連接的相應(yīng)寫操作;如果某個端口的 Socket 連接已經(jīng)中斷,則調(diào)用相應(yīng)的析構(gòu)方法關(guān)閉該端口。這樣能充分利用服務(wù)器資源,效率得到大幅度提高;
異步非阻塞 (AIO 方法,JDK7 發(fā)布):服務(wù)器實現(xiàn)模式為一個有效請求啟動一個線程,客戶端的 I/O 請求都是由操作系統(tǒng)先完成了再通知服務(wù)器應(yīng)用去啟動線程進行處理,每個線程不必親自處理 I/O,而是委派操作系統(tǒng)來處理,并且也不需要等待 I/O 完成,如果完成了操作系統(tǒng)會另行通知的。該模式采用了 Linux 的 epoll 模型。
在連接數(shù)不多的情況下,傳統(tǒng) I/O 模式編寫較為容易,使用上也較為簡單。但是隨著連接數(shù)的不斷增多,傳統(tǒng) I/O 處理每個連接都需要消耗一個線程,而程序的效率,當線程數(shù)不多時是隨著線程數(shù)的增加而增加,但是到一定的數(shù)量之后,是隨著線程數(shù)的增加而減少的。所以傳統(tǒng)阻塞式 I/O 的瓶頸在于不能處理過多的連接。非阻塞式 I/O 出現(xiàn)的目的就是為了解決這個瓶頸。非阻塞 IO 處理連接的線程數(shù)和連接數(shù)沒有聯(lián)系,例如系統(tǒng)處理 10000 個連接,非阻塞 I/O 不需要啟動 10000 個線程,你可以用 1000 個,也可以用 2000 個線程來處理。因為非阻塞 IO 處理連接是異步的,當某個連接發(fā)送請求到服務(wù)器,服務(wù)器把這個連接請求當作一個請求“事件”,并把這個“事件”分配給相應(yīng)的函數(shù)處理。我們可以把這個處理函數(shù)放到線程中去執(zhí)行,執(zhí)行完就把線程歸還,這樣一個線程就可以異步的處理多個事件。而阻塞式 I/O 的線程的大部分時間都被浪費在等待請求上了。
Java NIO
Java.nio 包是 Java 在 1.4 版本之后新增加的包,專門用來提高 I/O 操作的效率。
表 1 所示是 I/O 與 NIO 之間的對比內(nèi)容。
表 1. I/O VS NIO
I/O | NIO |
---|---|
面向流 | 面向緩沖 |
阻塞 IO | 非阻塞 IO |
無 | 選擇器 |
NIO 是基于塊 (Block) 的,它以塊為基本單位處理數(shù)據(jù)。在 NIO 中,最為重要的兩個組件是緩沖 Buffer 和通道 Channel。緩沖是一塊連續(xù)的內(nèi)存塊,是 NIO 讀寫數(shù)據(jù)的中轉(zhuǎn)地。通道標識緩沖數(shù)據(jù)的源頭或者目的地,它用于向緩沖讀取或者寫入數(shù)據(jù),是訪問緩沖的接口。Channel 是一個雙向通道,即可讀,也可寫。Stream 是單向的。應(yīng)用程序不能直接對 Channel 進行讀寫操作,而必須通過 Buffer 來進行,即 Channel 是通過 Buffer 來讀寫數(shù)據(jù)的。
使用 Buffer 讀寫數(shù)據(jù)一般遵循以下四個步驟:
當向 Buffer 寫入數(shù)據(jù)時,Buffer 會記錄下寫了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù),需要通過 flip() 方法將 Buffer 從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到 Buffer 的所有數(shù)據(jù)。
一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫入。有兩種方式能清空緩沖區(qū):調(diào)用 clear() 或 compact() 方法。clear() 方法會清空整個緩沖區(qū)。compact() 方法只會清除已經(jīng)讀過的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。
Buffer 有多種類型,不同的 Buffer 提供不同的方式操作 Buffer 中的數(shù)據(jù)。
圖 1 Buffer 接口層次圖
Buffer 寫數(shù)據(jù)有兩種情況:
從 Buffer 中讀取數(shù)據(jù)有兩種方式:
Buffer 的 rewin 方法將 position 設(shè)回 0,所以你可以重讀 Buffer 中的所有數(shù)據(jù)。limit 保持不變,仍然表示能從 Buffer 中讀取多少個元素(byte、char 等)。
clear() 和 compact() 方法
一旦讀完 Buffer 中的數(shù)據(jù),需要讓 Buffer 準備好再次被寫入。可以通過 clear() 或 compact() 方法來完成。
如果調(diào)用的是 clear() 方法,position 將被設(shè)回 0,limit 被設(shè)置成 capacity 的值。換句話說,Buffer 被清空了。Buffer 中的數(shù)據(jù)并未清除,只是這些標記告訴我們可以從哪里開始往 Buffer 里寫數(shù)據(jù)。
如果 Buffer 中有一些未讀的數(shù)據(jù),調(diào)用 clear() 方法,數(shù)據(jù)將“被遺忘”,意味著不再有任何標記會告訴你哪些數(shù)據(jù)被讀過,哪些還沒有。如果 Buffer 中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù),但是此時想要先寫些數(shù)據(jù),那么使用 compact() 方法。compact() 方法將所有未讀的數(shù)據(jù)拷貝到 Buffer 起始處。然后將 position 設(shè)到最后一個未讀元素正后面。limit 屬性依然像 clear() 方法一樣,設(shè)置成 capacity?,F(xiàn)在 Buffer 準備好寫數(shù)據(jù)了,但是不會覆蓋未讀的數(shù)據(jù)。
Buffer 參數(shù)
Buffer 有 3 個重要的參數(shù):位置 (position)、容量 (capacity) 和上限 (limit)。
capacity 是指 Buffer 的大小,在 Buffer 建立的時候已經(jīng)確定。
limit 當 Buffer 處于寫模式,指還可以寫入多少數(shù)據(jù);處于讀模式,指還有多少數(shù)據(jù)可以讀。
position 當 Buffer 處于寫模式,指下一個寫數(shù)據(jù)的位置;處于讀模式,當前將要讀取的數(shù)據(jù)的位置。每讀寫一個數(shù)據(jù),position+1,也就是 limit 和 position 在 Buffer 的讀/寫時的含義不一樣。當調(diào)用 Buffer 的 flip 方法,由寫模式變?yōu)樽x模式時,limit(讀)=position(寫),position(讀) =0。
散射&聚集
NIO 提供了處理結(jié)構(gòu)化數(shù)據(jù)的方法,稱之為散射 (Scattering) 和聚集 (Gathering)。散射是指將數(shù)據(jù)讀入一組 Buffer 中,而不僅僅是一個。聚集與之相反,指將數(shù)據(jù)寫入一組 Buffer 中。散射和聚集的基本使用方法和對單個 Buffer 操作時的使用方法相當類似。在散射讀取中,通道依次填充每個緩沖區(qū)。填滿一個緩沖區(qū)后,它就開始填充下一個,在某種意義上,緩沖區(qū)數(shù)組就像一個大緩沖區(qū)。在已知文件具體結(jié)構(gòu)的情況下,可以構(gòu)造若干個符合文件結(jié)構(gòu)的 Buffer,使得各個 Buffer 的大小恰好符合文件各段結(jié)構(gòu)的大小。此時,通過散射讀的方式可以一次將內(nèi)容裝配到各個對應(yīng)的 Buffer 中,從而簡化操作。如果需要創(chuàng)建指定格式的文件,只要先構(gòu)造好大小合適的 Buffer 對象,使用聚集寫的方式,便可以很快地創(chuàng)建出文件。清單 1 以 FileChannel 為例,展示如何使用散射和聚集讀寫結(jié)構(gòu)化文件。
清單 1. 使用散射和聚集讀寫結(jié)構(gòu)化文件
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 性能優(yōu)化技巧".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. 運行結(jié)果
java 性能優(yōu)化技巧 test
清單 3 所示代碼對傳統(tǒng) I/O、基于 Byte 的 NIO、基于內(nèi)存映射的 NIO 三種方式進行了性能上的對比,使用一個有 400 萬數(shù)據(jù)的文件的讀、寫操作耗時作為評測依據(jù)。
清單 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 個整數(shù) } 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));//將整數(shù)轉(zhuǎn)為數(shù)組 } 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);//讀取文件數(shù)據(jù) fc.close(); byteBuffer.flip();//準備讀取數(shù)據(jù) while(byteBuffer.hasRemaining()){ byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());//將 byte 轉(zhuǎn)為整數(shù) } } 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(); //將文件直接映射到內(nèi)存的方法 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 還提供了一個可以直接訪問系統(tǒng)物理內(nèi)存的類 DirectBuffer。DirectBuffer 繼承自 ByteBuffer,但和普通的 ByteBuffer 不同。普通的 ByteBuffer 仍然在 JVM 堆上分配空間,其最大內(nèi)存受到最大堆的限制,而 DirectBuffer 直接分配在物理內(nèi)存上,并不占用堆空間。在對普通的 ByteBuffer 訪問時,系統(tǒng)總是會使用一個“內(nèi)核緩沖區(qū)”進行間接的操作。而 DirectrBuffer 所處的位置,相當于這個“內(nèi)核緩沖區(qū)”。因此,使用 DirectBuffer 是一種更加接近系統(tǒng)底層的方法,所以,它的速度比普通的 ByteBuffer 更快。DirectBuffer 相對于 ByteBuffer 而言,讀寫訪問速度快很多,但是創(chuàng)建和銷毀 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);//創(chuàng)建 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);//創(chuàng)建 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 可知,頻繁創(chuàng)建和銷毀 DirectBuffer 的代價遠遠大于在堆上分配內(nèi)存空間。使用參數(shù)-XX:MaxDirectMemorySize=200M –Xmx200M 在 VM Arguments 里面配置最大 DirectBuffer 和最大堆空間,代碼中分別請求了 200M 的空間,如果設(shè)置的堆空間過小,例如設(shè)置 1M,會拋出錯誤如清單 7 所示。
清單 7. 運行錯誤
Error occurred during initialization of VM Too small initial heap for new size specified
DirectBuffer 的信息不會打印在 GC 里面,因為 GC 只記錄了堆空間的內(nèi)存回收??梢钥吹剑捎?ByteBuffer 在堆上分配空間,因此其 GC 數(shù)組相對非常頻繁,在需要頻繁創(chuàng)建 Buffer 的場合,由于創(chuàng)建和銷毀 DirectBuffer 的代碼比較高昂,不宜使用 DirectBuffer。但是如果能將 DirectBuffer 進行復(fù)用,可以大幅改善系統(tǒng)性能。清單 8 是一段對 DirectBuffer 進行監(jiān)控代碼。
清單 8. 對 DirectBuffer 監(jiān)控代碼
import java.lang.reflect.Field; public class monDirectBuffer { public static void main(String[] args){ try { Class c = Class.forName("java.nio.Bits");//通過反射取得私有數(shù)據(jù) 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 協(xié)議,其中 Netty 只是一個 NIO 框架,它不需要 Web 容器的額外支持,也就是說不限定 Web 容器。
Java AIO
AIO 相關(guān)的類和接口:
java.nio.channels.AsynchronousChannel:標記一個 Channel 支持異步 IO 操作; java.nio.channels.AsynchronousServerSocketChannel:ServerSocket 的 AIO 版本,創(chuàng)建 TCP 服務(wù)端,綁定地址,監(jiān)聽端口等; java.nio.channels.AsynchronousSocketChannel:面向流的異步 Socket Channel,表示一個連接; java.nio.channels.AsynchronousChannelGroup:異步 Channel 的分組管理,目的是為了資源共享。一個 AsynchronousChannelGroup 綁定一個線程池,這個線程池執(zhí)行兩個任務(wù):處理 IO 事件和派發(fā) CompletionHandler。AsynchronousServerSocketChannel 創(chuàng)建的時候可以傳入一個 AsynchronousChannelGroup,那么通過 AsynchronousServerSocketChannel 創(chuàng)建的 AsynchronousSocketChannel 將同屬于一個組,共享資源; java.nio.channels.CompletionHandler:異步 IO 操作結(jié)果的回調(diào)接口,用于定義在 IO 操作完成后所作的回調(diào)工作。AIO 的 API 允許兩種方式來處理異步操作的結(jié)果:返回的 Future 模式或者注冊 CompletionHandler,推薦用 CompletionHandler 的方式,這些 handler 的調(diào)用是由 AsynchronousChannelGroup 的線程池派發(fā)的。這里線程池的大小是性能的關(guān)鍵因素。
這里舉一個程序范例,簡單介紹一下 AIO 如何運作。
清單 10. 服務(wù)端程序
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)); //監(jiān)聽消息,收到后啟動 Handle 處理模塊 listener.accept(null, new CompletionHandler() { 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 函數(shù)
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);//由于是異步操作,所以睡眠一定時間,以免程序很快結(jié)束 } @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(); } } }
后續(xù)會專門出文章具體深入介紹 AIO 的源代碼、設(shè)計理念、設(shè)計模式等等。
結(jié)束語
I/O 與 NIO 一個比較重要的區(qū)別是我們使用 I/O 的時候往往會引入多線程,每個連接使用一個單獨的線程,而 NIO 則是使用單線程或者只使用少量的多線程,每個連接共用一個線程。而由于 NIO 的非阻塞需要一直輪詢,比較消耗系統(tǒng)資源,所以異步非阻塞模式 AIO 就誕生了。本文對 I/O、NIO、AIO 等三種輸入輸出操作方式進行一一介紹,力求通過簡單的描述和實例讓讀者能夠掌握基本的操作、優(yōu)化方法。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!