Java I/O 操作及優(yōu)化詳細(xì)介紹
概要:
流是一組有順序的,有起點(diǎn)和終點(diǎn)的字節(jié)集合,是對(duì)數(shù)據(jù)傳輸?shù)目偡Q或抽象。即數(shù)據(jù)在兩設(shè)備間的傳輸稱為流,流的本質(zhì)是數(shù)據(jù)傳輸,根據(jù)數(shù)據(jù)傳輸特性將流抽象為各種類,方便更直觀的進(jìn)行數(shù)據(jù)操作。
Java I/O
I/O,即 Input/Output(輸入/輸出) 的簡(jiǎn)稱。就 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)對(duì)上述模型支持不同,UNIX 支持 IO 多路復(fù)用。不同系統(tǒng)叫法不同,freebsd 里面叫 kqueue,Linux 叫 epoll。而 Windows2000 的時(shí)候就誕生了 IOCP 用以支持 asynchronous I/O。
Java 是一種跨平臺(tái)語(yǔ)言,為了支持異步 I/O,誕生了 NIO,Java1.4 引入的 NIO1.0 是基于 I/O 復(fù)用的,它在各個(gè)平臺(tái)上會(huì)選擇不同的復(fù)用方式。Linux 用的 epoll,BSD 上用 kqueue,Windows 上是重疊 I/O。
Java I/O 的相關(guān)方法如下所述:
同步并阻塞 (I/O 方法):服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接啟動(dòng)一個(gè)線程,每個(gè)線程親自處理 I/O 并且一直等待 I/O 直到完成,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理。但是如果這個(gè)連接不做任何事情就會(huì)造成不必要的線程開(kāi)銷,當(dāng)然可以通過(guò)線程池機(jī)制改善這個(gè)缺點(diǎn)。I/O 的局限是它是面向流的、阻塞式的、串行的一個(gè)過(guò)程。對(duì)每一個(gè)客戶端的 Socket 連接 I/O 都需要一個(gè)線程來(lái)處理,而且在此期間,這個(gè)線程一直被占用,直到 Socket 關(guān)閉。在這期間,TCP 的連接、數(shù)據(jù)的讀取、數(shù)據(jù)的返回都是被阻塞的。也就是說(shuō)這期間大量浪費(fèi)了 CPU 的時(shí)間片和線程占用的內(nèi)存資源。此外,每建立一個(gè) Socket 連接時(shí),同時(shí)創(chuàng)建一個(gè)新線程對(duì)該 Socket 進(jìn)行單獨(dú)通信 (采用阻塞的方式通信)。這種方式具有很快的響應(yīng)速度,并且控制起來(lái)也很簡(jiǎn)單。在連接數(shù)較少的時(shí)候非常有效,但是如果對(duì)每一個(gè)連接都產(chǎn)生一個(gè)線程無(wú)疑是對(duì)系統(tǒng)資源的一種浪費(fèi),如果連接數(shù)較多將會(huì)出現(xiàn)資源不足的情況;
同步非阻塞 (NIO 方法):服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求啟動(dòng)一個(gè)線程,每個(gè)線程親自處理 I/O,但是另外的線程輪詢檢查是否 I/O 準(zhǔn)備完畢,不必等待 I/O 完成,即客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有 I/O 請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。NIO 則是面向緩沖區(qū),非阻塞式的,基于選擇器的,用一個(gè)線程來(lái)輪詢監(jiān)控多個(gè)數(shù)據(jù)傳輸通道,哪個(gè)通道準(zhǔn)備好了 (即有一組可以處理的數(shù)據(jù)) 就處理哪個(gè)通道。服務(wù)器端保存一個(gè) Socket 連接列表,然后對(duì)這個(gè)列表進(jìn)行輪詢,如果發(fā)現(xiàn)某個(gè) Socket 端口上有數(shù)據(jù)可讀時(shí),則調(diào)用該 Socket 連接的相應(yīng)讀操作;如果發(fā)現(xiàn)某個(gè) Socket 端口上有數(shù)據(jù)可寫(xiě)時(shí),則調(diào)用該 Socket 連接的相應(yīng)寫(xiě)操作;如果某個(gè)端口的 Socket 連接已經(jīng)中斷,則調(diào)用相應(yīng)的析構(gòu)方法關(guān)閉該端口。這樣能充分利用服務(wù)器資源,效率得到大幅度提高;
異步非阻塞 (AIO 方法,JDK7 發(fā)布):服務(wù)器實(shí)現(xiàn)模式為一個(gè)有效請(qǐng)求啟動(dòng)一個(gè)線程,客戶端的 I/O 請(qǐng)求都是由操作系統(tǒng)先完成了再通知服務(wù)器應(yīng)用去啟動(dòng)線程進(jìn)行處理,每個(gè)線程不必親自處理 I/O,而是委派操作系統(tǒng)來(lái)處理,并且也不需要等待 I/O 完成,如果完成了操作系統(tǒng)會(huì)另行通知的。該模式采用了 Linux 的 epoll 模型。
在連接數(shù)不多的情況下,傳統(tǒng) I/O 模式編寫(xiě)較為容易,使用上也較為簡(jiǎn)單。但是隨著連接數(shù)的不斷增多,傳統(tǒng) I/O 處理每個(gè)連接都需要消耗一個(gè)線程,而程序的效率,當(dāng)線程數(shù)不多時(shí)是隨著線程數(shù)的增加而增加,但是到一定的數(shù)量之后,是隨著線程數(shù)的增加而減少的。所以傳統(tǒng)阻塞式 I/O 的瓶頸在于不能處理過(guò)多的連接。非阻塞式 I/O 出現(xiàn)的目的就是為了解決這個(gè)瓶頸。非阻塞 IO 處理連接的線程數(shù)和連接數(shù)沒(méi)有聯(lián)系,例如系統(tǒng)處理 10000 個(gè)連接,非阻塞 I/O 不需要啟動(dòng) 10000 個(gè)線程,你可以用 1000 個(gè),也可以用 2000 個(gè)線程來(lái)處理。因?yàn)榉亲枞?IO 處理連接是異步的,當(dāng)某個(gè)連接發(fā)送請(qǐng)求到服務(wù)器,服務(wù)器把這個(gè)連接請(qǐng)求當(dāng)作一個(gè)請(qǐng)求“事件”,并把這個(gè)“事件”分配給相應(yīng)的函數(shù)處理。我們可以把這個(gè)處理函數(shù)放到線程中去執(zhí)行,執(zhí)行完就把線程歸還,這樣一個(gè)線程就可以異步的處理多個(gè)事件。而阻塞式 I/O 的線程的大部分時(shí)間都被浪費(fèi)在等待請(qǐng)求上了。
Java NIO
Java.nio 包是 Java 在 1.4 版本之后新增加的包,專門(mén)用來(lái)提高 I/O 操作的效率。
表 1 所示是 I/O 與 NIO 之間的對(duì)比內(nèi)容。
表 1. I/O VS NIO
I/O | NIO |
---|---|
面向流 | 面向緩沖 |
阻塞 IO | 非阻塞 IO |
無(wú) | 選擇器 |
NIO 是基于塊 (Block) 的,它以塊為基本單位處理數(shù)據(jù)。在 NIO 中,最為重要的兩個(gè)組件是緩沖 Buffer 和通道 Channel。緩沖是一塊連續(xù)的內(nèi)存塊,是 NIO 讀寫(xiě)數(shù)據(jù)的中轉(zhuǎn)地。通道標(biāo)識(shí)緩沖數(shù)據(jù)的源頭或者目的地,它用于向緩沖讀取或者寫(xiě)入數(shù)據(jù),是訪問(wèn)緩沖的接口。Channel 是一個(gè)雙向通道,即可讀,也可寫(xiě)。Stream 是單向的。應(yīng)用程序不能直接對(duì) Channel 進(jìn)行讀寫(xiě)操作,而必須通過(guò) Buffer 來(lái)進(jìn)行,即 Channel 是通過(guò) Buffer 來(lái)讀寫(xiě)數(shù)據(jù)的。
使用 Buffer 讀寫(xiě)數(shù)據(jù)一般遵循以下四個(gè)步驟:
- 寫(xiě)入數(shù)據(jù)到 Buffer;
- 調(diào)用 flip() 方法;
- 從 Buffer 中讀取數(shù)據(jù);
- 調(diào)用 clear() 方法或者 compact() 方法。
當(dāng)向 Buffer 寫(xiě)入數(shù)據(jù)時(shí),Buffer 會(huì)記錄下寫(xiě)了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù),需要通過(guò) flip() 方法將 Buffer 從寫(xiě)模式切換到讀模式。在讀模式下,可以讀取之前寫(xiě)入到 Buffer 的所有數(shù)據(jù)。
一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫(xiě)入。有兩種方式能清空緩沖區(qū):調(diào)用 clear() 或 compact() 方法。clear() 方法會(huì)清空整個(gè)緩沖區(qū)。compact() 方法只會(huì)清除已經(jīng)讀過(guò)的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫(xiě)入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。
Buffer 有多種類型,不同的 Buffer 提供不同的方式操作 Buffer 中的數(shù)據(jù)。
圖 1 Buffer 接口層次圖
Buffer 寫(xiě)數(shù)據(jù)有兩種情況:
- 從 Channel 寫(xiě)到 Buffer,如例子中 Channel 從文件中讀取數(shù)據(jù),寫(xiě)到 Channel;
- 直接調(diào)用 put 方法,往里面寫(xiě)數(shù)據(jù)。
從 Buffer 中讀取數(shù)據(jù)有兩種方式:
- 從 Buffer 讀取數(shù)據(jù)到 Channel;
- 使用 get() 方法從 Buffer 中讀取數(shù)據(jù)。
Buffer 的 rewin 方法將 position 設(shè)回 0,所以你可以重讀 Buffer 中的所有數(shù)據(jù)。limit 保持不變,仍然表示能從 Buffer 中讀取多少個(gè)元素(byte、char 等)。
clear() 和 compact() 方法
一旦讀完 Buffer 中的數(shù)據(jù),需要讓 Buffer 準(zhǔn)備好再次被寫(xiě)入??梢酝ㄟ^(guò) clear() 或 compact() 方法來(lái)完成。
如果調(diào)用的是 clear() 方法,position 將被設(shè)回 0,limit 被設(shè)置成 capacity 的值。換句話說(shuō),Buffer 被清空了。Buffer 中的數(shù)據(jù)并未清除,只是這些標(biāo)記告訴我們可以從哪里開(kāi)始往 Buffer 里寫(xiě)數(shù)據(jù)。
如果 Buffer 中有一些未讀的數(shù)據(jù),調(diào)用 clear() 方法,數(shù)據(jù)將“被遺忘”,意味著不再有任何標(biāo)記會(huì)告訴你哪些數(shù)據(jù)被讀過(guò),哪些還沒(méi)有。如果 Buffer 中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù),但是此時(shí)想要先寫(xiě)些數(shù)據(jù),那么使用 compact() 方法。compact() 方法將所有未讀的數(shù)據(jù)拷貝到 Buffer 起始處。然后將 position 設(shè)到最后一個(gè)未讀元素正后面。limit 屬性依然像 clear() 方法一樣,設(shè)置成 capacity?,F(xiàn)在 Buffer 準(zhǔn)備好寫(xiě)數(shù)據(jù)了,但是不會(huì)覆蓋未讀的數(shù)據(jù)。
Buffer 參數(shù)
Buffer 有 3 個(gè)重要的參數(shù):位置 (position)、容量 (capacity) 和上限 (limit)。
capacity 是指 Buffer 的大小,在 Buffer 建立的時(shí)候已經(jīng)確定。
limit 當(dāng) Buffer 處于寫(xiě)模式,指還可以寫(xiě)入多少數(shù)據(jù);處于讀模式,指還有多少數(shù)據(jù)可以讀。
position 當(dāng) Buffer 處于寫(xiě)模式,指下一個(gè)寫(xiě)數(shù)據(jù)的位置;處于讀模式,當(dāng)前將要讀取的數(shù)據(jù)的位置。每讀寫(xiě)一個(gè)數(shù)據(jù),position+1,也就是 limit 和 position 在 Buffer 的讀/寫(xiě)時(shí)的含義不一樣。當(dāng)調(diào)用 Buffer 的 flip 方法,由寫(xiě)模式變?yōu)樽x模式時(shí),limit(讀)=position(寫(xiě)),position(讀) =0。
散射&聚集
NIO 提供了處理結(jié)構(gòu)化數(shù)據(jù)的方法,稱之為散射 (Scattering) 和聚集 (Gathering)。散射是指將數(shù)據(jù)讀入一組 Buffer 中,而不僅僅是一個(gè)。聚集與之相反,指將數(shù)據(jù)寫(xiě)入一組 Buffer 中。散射和聚集的基本使用方法和對(duì)單個(gè) Buffer 操作時(shí)的使用方法相當(dāng)類似。在散射讀取中,通道依次填充每個(gè)緩沖區(qū)。填滿一個(gè)緩沖區(qū)后,它就開(kāi)始填充下一個(gè),在某種意義上,緩沖區(qū)數(shù)組就像一個(gè)大緩沖區(qū)。在已知文件具體結(jié)構(gòu)的情況下,可以構(gòu)造若干個(gè)符合文件結(jié)構(gòu)的 Buffer,使得各個(gè) Buffer 的大小恰好符合文件各段結(jié)構(gòu)的大小。此時(shí),通過(guò)散射讀的方式可以一次將內(nèi)容裝配到各個(gè)對(duì)應(yīng)的 Buffer 中,從而簡(jiǎn)化操作。如果需要?jiǎng)?chuàng)建指定格式的文件,只要先構(gòu)造好大小合適的 Buffer 對(duì)象,使用聚集寫(xiě)的方式,便可以很快地創(chuàng)建出文件。清單 1 以 FileChannel 為例,展示如何使用散射和聚集讀寫(xiě)結(jié)構(gòu)化文件。
清單 1. 使用散射和聚集讀寫(xiě)結(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. 運(yùn)行結(jié)果
java 性能優(yōu)化技巧 test
清單 3 所示代碼對(duì)傳統(tǒng) I/O、基于 Byte 的 NIO、基于內(nèi)存映射的 NIO 三種方式進(jìn)行了性能上的對(duì)比,使用一個(gè)有 400 萬(wàn)數(shù)據(jù)的文件的讀、寫(xiě)操作耗時(shí)作為評(píng)測(cè)依據(jù)。
清單 3. I/O 的三種方式對(duì)比試驗(yàn)
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);//寫(xiě)入 4000000 個(gè)整數(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();//準(zhǔn)備寫(xiě) 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();//準(zhǔn)備讀取數(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));//最高位,無(wú)符號(hào)右移 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 運(yùn)行輸出如清單 4 所示。
清單 4. 運(yùn)行輸出
1139 906 296 157 234 125
除上述描述及清單 3 所示代碼以外,NIO 的 Buffer 還提供了一個(gè)可以直接訪問(wèn)系統(tǒng)物理內(nèi)存的類 DirectBuffer。DirectBuffer 繼承自 ByteBuffer,但和普通的 ByteBuffer 不同。普通的 ByteBuffer 仍然在 JVM 堆上分配空間,其最大內(nèi)存受到最大堆的限制,而 DirectBuffer 直接分配在物理內(nèi)存上,并不占用堆空間。在對(duì)普通的 ByteBuffer 訪問(wèn)時(shí),系統(tǒng)總是會(huì)使用一個(gè)“內(nèi)核緩沖區(qū)”進(jìn)行間接的操作。而 DirectrBuffer 所處的位置,相當(dāng)于這個(gè)“內(nèi)核緩沖區(qū)”。因此,使用 DirectBuffer 是一種更加接近系統(tǒng)底層的方法,所以,它的速度比普通的 ByteBuffer 更快。DirectBuffer 相對(duì)于 ByteBuffer 而言,讀寫(xiě)訪問(wèn)速度快很多,但是創(chuàng)建和銷毀 DirectrBuffer 的花費(fèi)卻比 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(); } }
運(yùn)行輸出如清單 6 所示。
清單 6. 運(yùn)行輸出
920 110 531 390
由清單 6 可知,頻繁創(chuàng)建和銷毀 DirectBuffer 的代價(jià)遠(yuǎn)遠(yuǎn)大于在堆上分配內(nèi)存空間。使用參數(shù)-XX:MaxDirectMemorySize=200M –Xmx200M 在 VM Arguments 里面配置最大 DirectBuffer 和最大堆空間,代碼中分別請(qǐng)求了 200M 的空間,如果設(shè)置的堆空間過(guò)小,例如設(shè)置 1M,會(huì)拋出錯(cuò)誤如清單 7 所示。
清單 7. 運(yùn)行錯(cuò)誤
Error occurred during initialization of VM Too small initial heap for new size specified
DirectBuffer 的信息不會(huì)打印在 GC 里面,因?yàn)?GC 只記錄了堆空間的內(nèi)存回收??梢钥吹?,由于 ByteBuffer 在堆上分配空間,因此其 GC 數(shù)組相對(duì)非常頻繁,在需要頻繁創(chuàng)建 Buffer 的場(chǎng)合,由于創(chuàng)建和銷毀 DirectBuffer 的代碼比較高昂,不宜使用 DirectBuffer。但是如果能將 DirectBuffer 進(jìn)行復(fù)用,可以大幅改善系統(tǒng)性能。清單 8 是一段對(duì) DirectBuffer 進(jìn)行監(jiān)控代碼。
清單 8. 對(duì) 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");//通過(guò)反射取得私有數(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(); } } }
運(yùn)行輸出如清單 9 所示。
清單 9. 運(yùn)行輸出
maxMemoryValue=67108864 reservedMemoryValue=0
由于 NIO 使用起來(lái)較為困難,所以許多公司推出了自己封裝 JDK NIO 的框架,例如 Apache 的 Mina,JBoss 的 Netty,Sun 的 Grizzly 等等,這些框架都直接封裝了傳輸層的 TCP 或 UDP 協(xié)議,其中 Netty 只是一個(gè) NIO 框架,它不需要 Web 容器的額外支持,也就是說(shuō)不限定 Web 容器。
Java AIO
AIO 相關(guān)的類和接口:
java.nio.channels.AsynchronousChannel:標(biāo)記一個(gè) Channel 支持異步 IO 操作; java.nio.channels.AsynchronousServerSocketChannel:ServerSocket 的 AIO 版本,創(chuàng)建 TCP 服務(wù)端,綁定地址,監(jiān)聽(tīng)端口等; java.nio.channels.AsynchronousSocketChannel:面向流的異步 Socket Channel,表示一個(gè)連接; java.nio.channels.AsynchronousChannelGroup:異步 Channel 的分組管理,目的是為了資源共享。一個(gè) AsynchronousChannelGroup 綁定一個(gè)線程池,這個(gè)線程池執(zhí)行兩個(gè)任務(wù):處理 IO 事件和派發(fā) CompletionHandler。AsynchronousServerSocketChannel 創(chuàng)建的時(shí)候可以傳入一個(gè) AsynchronousChannelGroup,那么通過(guò) AsynchronousServerSocketChannel 創(chuàng)建的 AsynchronousSocketChannel 將同屬于一個(gè)組,共享資源; java.nio.channels.CompletionHandler:異步 IO 操作結(jié)果的回調(diào)接口,用于定義在 IO 操作完成后所作的回調(diào)工作。AIO 的 API 允許兩種方式來(lái)處理異步操作的結(jié)果:返回的 Future 模式或者注冊(cè) CompletionHandler,推薦用 CompletionHandler 的方式,這些 handler 的調(diào)用是由 AsynchronousChannelGroup 的線程池派發(fā)的。這里線程池的大小是性能的關(guān)鍵因素。
這里舉一個(gè)程序范例,簡(jiǎn)單介紹一下 AIO 如何運(yùn)作。
清單 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)聽(tīng)消息,收到后啟動(dòng) Handle 處理模塊 listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { public void completed(AsynchronousSocketChannel ch, Void att) { listener.accept(null, this);// 接受下一個(gè)連接 handle(ch);// 處理當(dāng)前連接 } @Override public void failed(Throwable exc, Void attachment) { // TODO Auto-generated method stub } }); } public void handle(AsynchronousSocketChannel ch) { ByteBuffer byteBuffer = ByteBuffer.allocate(32);//開(kāi)一個(gè) 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 寫(xiě)入讀取到的字符 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);//由于是異步操作,所以睡眠一定時(shí)間,以免程序很快結(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ù)會(huì)專門(mén)出文章具體深入介紹 AIO 的源代碼、設(shè)計(jì)理念、設(shè)計(jì)模式等等。
結(jié)束語(yǔ)
I/O 與 NIO 一個(gè)比較重要的區(qū)別是我們使用 I/O 的時(shí)候往往會(huì)引入多線程,每個(gè)連接使用一個(gè)單獨(dú)的線程,而 NIO 則是使用單線程或者只使用少量的多線程,每個(gè)連接共用一個(gè)線程。而由于 NIO 的非阻塞需要一直輪詢,比較消耗系統(tǒng)資源,所以異步非阻塞模式 AIO 就誕生了。本文對(duì) I/O、NIO、AIO 等三種輸入輸出操作方式進(jìn)行一一介紹,力求通過(guò)簡(jiǎn)單的描述和實(shí)例讓讀者能夠掌握基本的操作、優(yōu)化方法。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
java調(diào)用Hbase報(bào)錯(cuò)解決方法
這篇文章主要為大家介紹了java調(diào)用Hbase報(bào)錯(cuò)解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-1070行Java代碼實(shí)現(xiàn)深度神經(jīng)網(wǎng)絡(luò)算法分享
這篇文章主要介紹了70行Java代碼實(shí)現(xiàn)深度神經(jīng)網(wǎng)絡(luò)算法分享,涉及神經(jīng)網(wǎng)絡(luò)的計(jì)算過(guò)程,神經(jīng)網(wǎng)絡(luò)的算法程序?qū)崿F(xiàn),多層神經(jīng)網(wǎng)絡(luò)完整程序?qū)崿F(xiàn)等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以參考下。2017-11-11java對(duì)象中什么時(shí)候適合用static修飾符踩坑解決記錄
這篇文章主要為大家介紹了java對(duì)象中什么時(shí)候適合用static修飾符踩坑解決記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Java數(shù)據(jù)結(jié)構(gòu)與算法實(shí)現(xiàn)遞歸與回溯
本文主要介紹了Java數(shù)據(jù)結(jié)構(gòu)與算法實(shí)現(xiàn)遞歸與回溯,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03java用兩個(gè)例子充分闡述多態(tài)的可拓展性介紹
下面小編就為大家?guī)?lái)一篇java用兩個(gè)例子充分闡述多態(tài)的可拓展性介紹。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06java?中如何實(shí)現(xiàn)?List?集合去重
這篇文章主要介紹了java?中如何實(shí)現(xiàn)?List?集合去重,List?去重指的是將?List?中的重復(fù)元素刪除掉的過(guò)程,下文操作操作過(guò)程介紹需要的小伙伴可以參考一下2022-05-05SpringBoot實(shí)現(xiàn)定時(shí)發(fā)送郵件的三種方法案例詳解
這篇文章主要介紹了SpringBoot三種方法實(shí)現(xiàn)定時(shí)發(fā)送郵件的案例,Spring框架的定時(shí)任務(wù)調(diào)度功能支持配置和注解兩種方式Spring?Boot在Spring框架的基礎(chǔ)上實(shí)現(xiàn)了繼承,并對(duì)其中基于注解方式的定時(shí)任務(wù)實(shí)現(xiàn)了非常好的支持,本文給大家詳細(xì)講解,需要的朋友可以參考下2023-03-03