您好,歡迎來電子發(fā)燒友網(wǎng)! ,新用戶?[免費注冊]

您的位置:電子發(fā)燒友網(wǎng)>源碼下載>java源碼下載>

Java I/O 的相關(guān)方法分析

大?。?/span>0.3 MB 人氣: 2017-09-27 需要積分:2

  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 多路復用。不同系統(tǒng)叫法不同,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 的相關(guān)方法如下所述:

  同步并阻塞 (I/O 方法):服務器實現(xiàn)模式為一個連接啟動一個線程,每個線程親自處理 I/O 并且一直等待 I/O 直到完成,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理。但是如果這個連接不做任何事情就會造成不必要的線程開銷,當然可以通過線程池機制改善這個缺點。I/O 的局限是它是面向流的、阻塞式的、串行的一個過程。對每一個客戶端的 Socket 連接 I/O 都需要一個線程來處理,而且在此期間,這個線程一直被占用,直到 Socket 關(guān)閉。在這期間,TCP 的連接、數(shù)據(jù)的讀取、數(shù)據(jù)的返回都是被阻塞的。也就是說這期間大量浪費了 CPU 的時間片和線程占用的內(nèi)存資源。此外,每建立一個 Socket 連接時,同時創(chuàng)建一個新線程對該 Socket 進行單獨通信 (采用阻塞的方式通信)。這種方式具有很快的響應速度,并且控制起來也很簡單。在連接數(shù)較少的時候非常有效,但是如果對每一個連接都產(chǎn)生一個線程無疑是對系統(tǒng)資源的一種浪費,如果連接數(shù)較多將會出現(xiàn)資源不足的情況;

  同步非阻塞 (NIO 方法):服務器實現(xiàn)模式為一個請求啟動一個線程,每個線程親自處理 I/O,但是另外的線程輪詢檢查是否 I/O 準備完畢,不必等待 I/O 完成,即客戶端發(fā)送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有 I/O 請求時才啟動一個線程進行處理。NIO 則是面向緩沖區(qū),非阻塞式的,基于選擇器的,用一個線程來輪詢監(jiān)控多個數(shù)據(jù)傳輸通道,哪個通道準備好了 (即有一組可以處理的數(shù)據(jù)) 就處理哪個通道。服務器端保存一個 Socket 連接列表,然后對這個列表進行輪詢,如果發(fā)現(xiàn)某個 Socket 端口上有數(shù)據(jù)可讀時,則調(diào)用該 Socket 連接的相應讀操作;如果發(fā)現(xiàn)某個 Socket 端口上有數(shù)據(jù)可寫時,則調(diào)用該 Socket 連接的相應寫操作;如果某個端口的 Socket 連接已經(jīng)中斷,則調(diào)用相應的析構(gòu)方法關(guān)閉該端口。這樣能充分利用服務器資源,效率得到大幅度提高;

  異步非阻塞 (AIO 方法,JDK7 發(fā)布):服務器實現(xiàn)模式為一個有效請求啟動一個線程,客戶端的 I/O 請求都是由操作系統(tǒ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ā)送請求到服務器,服務器把這個連接請求當作一個請求“事件”,并把這個“事件”分配給相應的函數(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

  Java I/O 的相關(guān)方法分析

  NIO 是基于塊 (Block) 的,它以塊為基本單位處理數(shù)據(jù)。在 NIO 中,最為重要的兩個組件是緩沖 Buffer 和通道 Channel。緩沖是一塊連續(xù)的內(nèi)存塊,是 NIO 讀寫數(shù)據(jù)的中轉(zhuǎn)地。通道標識緩沖數(shù)據(jù)的源頭或者目的地,它用于向緩沖讀取或者寫入數(shù)據(jù),是訪問緩沖的接口。Channel 是一個雙向通道,即可讀,也可寫。Stream 是單向的。應用程序不能直接對 Channel 進行讀寫操作,而必須通過 Buffer 來進行,即 Channel 是通過 Buffer 來讀寫數(shù)據(jù)的。

  使用 Buffer 讀寫數(shù)據(jù)一般遵循以下四個步驟:

  1. 寫入數(shù)據(jù)到 Buffer;

  2. 調(diào)用 flip() 方法;

  3. 從 Buffer 中讀取數(shù)據(jù);

  4. 調(diào)用 clear() 方法或者 compact() 方法。

  當向 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 接口層次圖

  Java I/O 的相關(guān)方法分析

  Buffer 寫數(shù)據(jù)有兩種情況:

  1. 從 Channel 寫到 Buffer,如例子中 Channel 從文件中讀取數(shù)據(jù),寫到 Channel;

  2. 直接調(diào)用 put 方法,往里面寫數(shù)據(jù)。

  從 Buffer 中讀取數(shù)據(jù)有兩種方式:

  1. 從 Buffer 讀取數(shù)據(jù)到 Channel;

  2. 使用 get() 方法從 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)容裝配到各個對應的 Buffer 中,從而簡化操作。如果需要創(chuàng)建指定格式的文件,只要先構(gòu)造好大小合適的 Buffer 對象,使用聚集寫的方式,便可以很快地創(chuàng)建出文件。清單 1 以 FileChannel 為例,展示如何使用散射和聚集讀寫結(jié)構(gòu)化文件。

  清單 1. 使用散射和聚集讀寫結(jié)構(gòu)化文件

  importjava.io.File;importjava.io.FileInputStream;importjava.io.FileNotFoundException;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.UnsupportedEncodingException;importjava.nio.ByteBuffer;importjava.nio.channels.FileChannel;publicclassNIOScatteringandGathering{publicvoidcreateFiles(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 = newByteBuffer[]{bookBuf,autBuf}; File file = newFile(TPATH); if(!file.exists()){ try{ file.createNewFile(); }catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } } try{ FileOutputStream fos = newFileOutputStream(file); FileChannel fc = fos.getChannel(); fc.write(bufs); fos.close(); } catch(FileNotFoundException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } ByteBuffer b1 = ByteBuffer.allocate(booklen); ByteBuffer b2 = ByteBuffer.allocate(autlen); ByteBuffer[] bufs1 = newByteBuffer[]{b1,b2}; File file1 =newFile(TPATH); try{ FileInputStream fis = newFileInputStream(file); FileChannel fc = fis.getChannel(); fc.read(bufs1); String bookname = newString(bufs1[ 0].array(), “utf-8”); String autname = newString(bufs1[ 1].array(), “utf-8”); System.out.println(bookname+ “ ”+autname); } catch(FileNotFoundException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } } catch(UnsupportedEncodingException e) { // TODO Auto-generated catch blocke.printStackTrace(); } } publicstaticvoidmain(String[] args){ NIOScatteringandGathering nio = newNIOScatteringandGathering(); 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 的三種方式對比試驗

  importjava.io.BufferedInputStream; importjava.io.BufferedOutputStream;importjava.io.DataInputStream; importjava.io.DataOutputStream; importjava.io.File;importjava.io.FileInputStream; importjava.io.FileNotFoundException;importjava.io.FileOutputStream; importjava.io.IOException;importjava.io.RandomAccessFile; importjava.nio.ByteBuffer; importjava.nio.IntBuffer;importjava.nio.MappedByteBuffer; importjava.nio.channels.FileChannel;publicclassNIOComparator{publicvoidIOMethod(String TPATH){ longstart = System.currentTimeMillis(); try{ DataOutputStream dos = newDataOutputStream(newBufferedOutputStream( newFileOutputStream( newFile(TPATH)))); for( inti= 0;i《4000000;i++){ dos.writeInt(i); //寫入 4000000 個整數(shù)} if(dos!= null){ dos.close(); } }catch(FileNotFoundException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } longend = System.currentTimeMillis(); System.out.println(end - start); start = System.currentTimeMillis(); try{ DataInputStream dis =newDataInputStream( newBufferedInputStream( newFileInputStream( newFile(TPATH))));for( inti= 0;i《 4000000;i++){ dis.readInt(); } if(dis!= null){ dis.close(); } }catch(FileNotFoundException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start); } publicvoidByteMethod(String TPATH){ longstart = System.currentTimeMillis(); try{ FileOutputStream fout = newFileOutputStream( newFile(TPATH)); FileChannel fc = fout.getChannel(); //得到文件通道ByteBuffer byteBuffer = ByteBuffer.allocate( 4000000*4); //分配 Bufferfor( inti= 0;i《 4000000;i++){ byteBuffer.put(int2byte(i)); //將整數(shù)轉(zhuǎn)為數(shù)組} byteBuffer.flip(); //準備寫fc.write(byteBuffer); } catch(FileNotFoundException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } longend = System.currentTimeMillis(); System.out.println(end - start); start = System.currentTimeMillis(); FileInputStream fin; try{ fin = newFileInputStream( newFile(TPATH)); FileChannel fc = fin.getChannel(); //取得文件通道ByteBuffer byteBuffer = ByteBuffer.allocate( 4000000* 4); //分配 Bufferfc.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 blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start); } publicvoidmapMethod(String TPATH){ longstart = System.currentTimeMillis(); //將文件直接映射到內(nèi)存的方法try{ FileChannel fc = newRandomAccessFile(TPATH,“rw”).getChannel(); IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0,4000000* 4).asIntBuffer(); for( inti= 0;i《 4000000;i++){ ib.put(i); } if(fc!= null){ fc.close(); } }catch(FileNotFoundException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } longend = System.currentTimeMillis(); System.out.println(end - start); start = System.currentTimeMillis(); try{ FileChannel fc =newFileInputStream(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 blocke.printStackTrace(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } end = System.currentTimeMillis(); System.out.println(end - start); } publicstaticbyte[] int2byte( intres){ byte[] targets = newbyte[ 4]; targets[ 3] = ( byte)(res & 0xff); //最低位targets[ 2] = ( byte)((res》》 8)& 0xff); //次低位targets[ 1] = ( byte)((res》》 16)& 0xff); //次高位targets[ 0] = ( byte)((res》》》 24)); //最高位,無符號右移returntargets; } publicstaticintbyte2int( byteb1, byteb2, byteb3, byteb4){ return((b1 & 0xff)《《 24)|((b2 & 0xff)《《 16)|((b3 & 0xff)《《 8)|(b4 & 0xff); } publicstaticvoidmain(String[] args){ NIOComparator nio = newNIOComparator(); 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

  importjava.nio.ByteBuffer; /** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassDirectBuffervsByteBuffer{publicvoidDirectBufferPerform(){ longstart = System.currentTimeMillis(); ByteBuffer bb = ByteBuffer.allocateDirect( 500); //分配 DirectBufferfor( inti= 0;i《 100000;i++){ for( intj= 0;j《 99;j++){ bb.putInt(j); } bb.flip(); for(intj= 0;j《 99;j++){ bb.getInt(j); } } bb.clear(); longend = System.currentTimeMillis(); System.out.println(end-start); start = System.currentTimeMillis(); for( inti= 0;i《 20000;i++){ ByteBuffer b = ByteBuffer.allocateDirect( 10000); //創(chuàng)建 DirectBuffer} end = System.currentTimeMillis(); System.out.println(end-start); } publicvoidByteBufferPerform(){longstart = System.currentTimeMillis(); ByteBuffer bb = ByteBuffer.allocate( 500); //分配 DirectBufferfor( inti= 0;i《 100000;i++){ for( intj= 0;j《 99;j++){ bb.putInt(j); } bb.flip(); for(intj= 0;j《 99;j++){ bb.getInt(j); } } bb.clear(); longend = System.currentTimeMillis(); System.out.println(end-start); start = System.currentTimeMillis(); for( inti= 0;i《 20000;i++){ ByteBuffer b = ByteBuffer.allocate( 10000); //創(chuàng)建 ByteBuffer} end = System.currentTimeMillis(); System.out.println(end-start); } publicstaticvoidmain(String[] args){ DirectBuffervsByteBuffer db = newDirectBuffervsByteBuffer(); 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. 運行錯誤

  Erroroccurred during initialization ofVM Too small initial heap fornewsize specified

  DirectBuffer 的信息不會打印在 GC 里面,因為 GC 只記錄了堆空間的內(nèi)存回收。可以看到,由于 ByteBuffer 在堆上分配空間,因此其 GC 數(shù)組相對非常頻繁,在需要頻繁創(chuàng)建 Buffer 的場合,由于創(chuàng)建和銷毀 DirectBuffer 的代碼比較高昂,不宜使用 DirectBuffer。但是如果能將 DirectBuffer 進行復用,可以大幅改善系統(tǒng)性能。清單 8 是一段對 DirectBuffer 進行監(jiān)控代碼。

  清單 8. 對 DirectBuffer 監(jiān)控代碼

  importjava.lang.reflect.Field; /** * Java學習交流QQ群:589809992 我們一起學Java! */publicclassmonDirectBuffer{publicstaticvoidmain(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 blocke.printStackTrace(); } catch(SecurityException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(NoSuchFieldException e) {// TODO Auto-generated catch blocke.printStackTrace(); } catch(IllegalArgumentException e) { // TODO Auto-generated catch blocke.printStackTrace(); }catch(IllegalAccessException e) { // TODO Auto-generated catch blocke.printStackTrace(); } } }

  運行輸出如清單 9 所示。

  清單 9. 運行輸出

  maxMemoryValue=67108864reservedMemoryValue=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 服務端,綁定地址,監(jiān)聽端口等;

  - java.nio.channels.AsynchronousSocketChannel:面向流的異步 Socket Channel,表示一個連接;

  - java.nio.channels.AsynchronousChannelGroup:異步 Channel 的分組管理,目的是為了資源共享。一個 AsynchronousChannelGroup 綁定一個線程池,這個線程池執(zhí)行兩個任務:處理 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. 服務端程序

  importjava.io.IOException; importjava.net.InetSocketAddress; importjava.nio.ByteBuffer;importjava.nio.channels.AsynchronousServerSocketChannel;importjava.nio.channels.AsynchronousSocketChannel;importjava.nio.channels.CompletionHandler;importjava.util.concurrent.ExecutionException;publicclassSimpleServer{publicSimpleServer( intport) throwsIOException {finalAsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind( newInetSocketAddress(port)); //監(jiān)聽消息,收到后啟動 Handle 處理模塊listener.accept( null,newCompletionHandler《AsynchronousSocketChannel, Void》() {publicvoidcompleted(AsynchronousSocketChannel ch, Void att) { listener.accept( null,this); // 接受下一個連接 handle(ch); // 處理當前連接 }@Overridepublicvoidfailed(Throwable exc, Void attachment) { // TODO Auto-generated method stub} }); } publicvoidhandle(AsynchronousSocketChannel ch) { ByteBuffer byteBuffer = ByteBuffer.allocate( 32); //開一個 Buffer try{ ch.read(byteBuffer).get(); //讀取輸入 } catch(InterruptedException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(ExecutionException e) { // TODO Auto-generated catch blocke.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ù)

  importjava.io.IOException; importjava.util.concurrent.ExecutionException;importorg.junit.Test; publicclassAIODemoTest{@TestpublicvoidtestServer()throwsIOException, InterruptedException { SimpleServer server = newSimpleServer(9021); Thread.sleep( 10000); //由于是異步操作,所以睡眠一定時間,以免程序很快結(jié)束}@TestpublicvoidtestClient() throwsIOException, InterruptedException, ExecutionException { SimpleClientClass client = newSimpleClientClass( “l(fā)ocalhost”,9021); client.write(( byte) 11); } publicstaticvoidmain(String[] args){ AIODemoTest demoTest = newAIODemoTest(); try{ demoTest.testServer(); } catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); } catch(InterruptedException e) { // TODO Auto-generated catch blocke.printStackTrace(); } try{ demoTest.testClient(); }catch(IOException e) { // TODO Auto-generated catch blocke.printStackTrace(); }catch(InterruptedException e) { // TODO Auto-generated catch blocke.printStackTrace(); }catch(ExecutionException e) { // TODO Auto-generated catch blocke.printStackTrace(); } } }

  后續(xù)會專門出文章具體深入介紹 AIO 的源代碼、設(shè)計理念、設(shè)計模式等等。

  結(jié)束語

  I/O 與 NIO 一個比較重要的區(qū)別是我們使用 I/O 的時候往往會引入多線程,每個連接使用一個單獨的線程,而 NIO 則是使用單線程或者只使用少量的多線程,每個連接共用一個線程。而由于 NIO 的非阻塞需要一直輪詢,比較消耗系統(tǒng)資源,所以異步非阻塞模式 AIO 就誕生了。本文對 I/O、NIO、AIO 等三種輸入輸出操作方式進行一一介紹,力求通過簡單的描述和實例讓讀者能夠掌握基本的操作、優(yōu)化方法。

非常好我支持^.^

(0) 0%

不好我反對

(0) 0%

      發(fā)表評論

      用戶評論
      評價:好評中評差評

      發(fā)表評論,獲取積分! 請遵守相關(guān)規(guī)定!

      ?