程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> NIO提升系統性能,nio系統性能

NIO提升系統性能,nio系統性能

編輯:JAVA綜合教程

NIO提升系統性能,nio系統性能


  • 前言

  在軟件系統中,I/O的速度要比內存的速度慢很多,因此I/O經常會稱為系統的瓶頸。所有,提高I/O速度,對於提升系統的整體性能有很大的作用。

  在java標准的I/O中,是基於流的I/O的實現,即InputStream和OutPutStream,這種基於流的實現以字節為基本單元,很容易實現各種過濾器。

  NIO和new I/O的簡稱,在java1.4納入JDK中,具有以下特征:

  1、為所有的原始類型提供(buffer)緩存支持;

  2、使用Charset作為字符集編碼解碼解決方案;

  3、增加了通道(Channel)對象,作為新的原始I/O抽象;

  4、支持鎖和內存訪問文件的文件訪問接口;

  5、提供了基於Selector的異步網絡I/O;

  NIO是基於塊(Block)的,它以塊為基本單位處理數據。在NIO中,最重要的兩個組件是buffer緩沖和channel通道。緩沖是一塊連續的內存區域,是NIO讀寫數據的中轉站。通道表示緩沖數據的源頭或目的地,它用於向緩沖讀取或寫入數據,是訪問緩沖的接口。通道和緩沖的關系如圖:

  

  • NIO中的Buffer類和Channel

  JDK為每一種java原生類型都提供了一種Buffer,除了ByteBuffer外,其他每一種Buffer都具有完全一樣的操作,除了操作類型不一樣以外。ByteBuffer可以用於絕大多數標准I/O操作的接口。

  在NIO中和Buffer配合使用的還有Channel。Channel是一個雙向通道,既可以讀也可以寫。有點類似Stream,但是Stream是單向的。應用程序不能直接對Channel進行讀寫操作,而必須通過Buffer來進行。

  下面以一個文件復制為例,簡單介紹NIO的Buffer和Channel的用法,代碼如下:

 1 public class NioCopyFileTest {
 2     public static void main(String[] args) throws Exception {
 3         NioCopyFileTest.copy("test.txt", "test2.txt");
 4     }
 5     
 6     public static void copy(String resource,String destination) throws Exception{
 7         FileInputStream fis = new FileInputStream(resource); 
 8         FileOutputStream fos = new FileOutputStream(destination);
 9         
10         FileChannel inputFileChannel = fis.getChannel();//讀文件通道
11         FileChannel outputFileChannel = fos.getChannel();//寫文件通道
12         ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//讀寫數據緩沖
13         while(true){
14             byteBuffer.clear();
15             int length =  inputFileChannel.read(byteBuffer);//讀取數據
16             if(length == -1){
17                 break;//讀取完畢
18             }
19             byteBuffer.flip();
20             outputFileChannel.write(byteBuffer);//寫入數據
21         }
22         inputFileChannel.close();
23         outputFileChannel.close();
24     }
25 }

  代碼中注釋寫的很詳細了,輸入流和輸出流都對應一個Channel通道,將數據通過讀文件channel讀取到緩沖中,然後再通過寫文件channel寫入到緩沖中。這樣就完成了文件復制。注意:緩沖在文件傳輸中起到的作用十分大,可以緩解內存和硬盤之間的性能差異,提升系統性能。

  • Buffer的基本原理

  Buffer有三個重要的參數:位置(position)、容量(capactiy)和上限(limit)。這三個參數的含義如下圖:

  下面例子很好的解釋了Buffer的工作原理:

 1      ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩沖區大小為15
 2         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());
 3         for (int i = 0; i < 10; i++) {
 4             buffer.put((byte) i);
 5         }
 6         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());
 7         buffer.flip();//重置position
 8         for (int i = 0; i < 5; i++) {
 9             System.out.println(buffer.get());
10         }
11         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());
12         buffer.flip();
13         System.out.println("position:"+buffer.position()+"limit:"+buffer.limit()+"capacity"+buffer.capacity());

  以上代碼,先分配了15個字節大小的緩沖區。在初始階段,position為0,capacity為15,limit為15。注意,position是從0開始的,所以索引為15的位置實際上是不存在的。

  接著往緩沖區放入10個元素,position始終指向下一個即將放入的位置,所有position為10,capacity和limit依然為15。

  進行flip()操作,會重置position的位置,並且將limit設置到當前position的位置,這時Buffer從寫模式進入讀模式,這樣就可以防止讀操作讀取到沒有進行操作的位置。所有此時,position為0,limit為10,capacity為15。

  接著進行五次讀操作,讀操作會設置position的位置,所以,position為5,limit為10,capacity為15。

  在進行一次flip()操作,此時可想而知position為0,limit為5,capacity為15。

  • Buffer的相關操作

  Buffer是NIO中最核心的對象,它的一系列的操作和使用也需要重點掌握,這裡簡單概括一下,也可以參考相關API查看。

  1、Buffer的創建:

  buffer的常見有兩種方式,使用靜態方法allocate()從堆中分配緩沖區,或者從一個既有數組中創建緩沖區。

1 ByteBuffer buffer = ByteBuffer.allocate(1024);//從堆中分配
2 byte[] arrays = new byte[1024];//從既有數組中創建
3 ByteBuffer buffer2 = ByteBuffer.wrap(arrays);

  2、重置或清空緩沖區:

  buffer還提供了一些用於重置和清空緩沖區的方法:rewind(),clear(),flip()。它們的作用如下:

  3、讀寫緩沖區:

  對Buffer對象進行讀寫操作是Buffer最重要的操作,buffer提供了許多讀寫操作的緩沖區。具體參考API。

  4、標志緩沖區

  標志(mark)緩沖區是一個在數據處理時很有用的功能,它就像書簽一樣,可以在數據處理中隨時記錄當前位置,然後再任意時刻回到這個位置,從而簡化或加快數據處理的流程。相關函數為:mark()和reset()。mark()用於記錄當前位置,reset()用於恢復到mark標記的位置。

  代碼如下:

 1 ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩沖區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         buffer.flip();//重置position
 6         for (int i = 0; i < buffer.limit(); i++) {
 7             System.out.print(buffer.get());
 8             if(i==4){
 9                 buffer.mark();
10                 System.out.print("mark at"+i);
11             }
12         }
13         System.out.println();
14         buffer.reset();
15         while(buffer.hasRemaining()){
16             System.out.print(buffer.get());
17 }

  輸出結果:

1 01234mark at456789
2 56789

  5、復制緩沖區

  復制緩沖區是以原緩沖區為基礎,生成一個完全一樣的緩沖區。方法為:duplicate()。這個函數對於處理復雜的Buffer數據很有好處。因為新生成的緩沖區和元緩沖區共享相同的內存數據。並且,任意一方的改動都是互相可見的,但是兩者又各自維護者自己的position、limit和capacity。這大大增加了程序的靈活性,為多方同時處理數據提供了可能。

  代碼如下:

 1         ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩沖區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         ByteBuffer buffer2 = buffer.duplicate();//復制當前緩沖區
 6         System.out.println("after buffer duplicate");
 7         System.out.println(buffer);
 8         System.out.println(buffer2);
 9         buffer2.flip();
10         System.out.println("after buffer2 flip");
11         System.out.println(buffer);
12         System.out.println(buffer2);
13         buffer2.put((byte)100);
14         System.out.println("after buffer2 put");
15         System.out.println(buffer.get(0));
16         System.out.println(buffer2.get(0));             

  輸出結果如下:

1 after buffer duplicate
2 java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
3 java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
4 after buffer2 flip
5 java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
6 java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
7 after buffer2 put
8 100
9 100

  6、緩沖區分片

  緩沖區分片使用slice()方法,它將現有的緩沖區創建新的子緩沖區,子緩沖區和父緩沖區共享數據,子緩沖區具有完整的緩沖區模型結構。當處理一個buffer的一個片段時,可以使用一個slice()方法取得一個子緩沖區,然後就像處理普通緩沖區一樣處理這個子緩沖區,而無需考慮邊界問題,這樣有助於系統模塊化。 

 1     ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩沖區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         buffer.position(2);
 6         buffer.limit(6);
 7         ByteBuffer subBuffer = buffer.slice();//復制緩沖區
 8         for (int i = 0; i < subBuffer.limit(); i++) {
 9             byte b = subBuffer.get(i);
10             b=(byte) (b*10);
11             subBuffer.put(i, b);
12         }
13         buffer.limit(buffer.capacity());
14         buffer.position(0);
15         for (int i = 0; i < buffer.limit(); i++) {
16             System.out.print(buffer.get(i)+" ");
17         }

  輸出結果: 

1 0 1 20 30 40 50 6 7 8 9 0 0 0 0 0 

  7、只讀緩沖區

  可以使用緩沖區對象的asReadOnlyBuffer()方法得到一個與當前緩沖區一致的,並且共享內存數據的只讀緩沖區,只讀緩沖區對於數據安全非常有用。使用只讀緩沖區可以保證數據不被修改,同時,只讀緩沖區和原始緩沖區是共享內存塊的,因此,對於原始緩沖區的修改,只讀緩沖區也是可見的。

  代碼如下:

 1      ByteBuffer buffer = ByteBuffer.allocate(15);//設置緩沖區大小為15
 2         for (int i = 0; i < 10; i++) {
 3             buffer.put((byte) i);
 4         }
 5         ByteBuffer readBuffer = buffer.asReadOnlyBuffer();
 6         for (int i = 0; i < readBuffer.limit(); i++) {
 7             System.out.print(readBuffer.get(i)+" ");
 8         }
 9         System.out.println();
10         buffer.put(2, (byte)20);
11         for (int i = 0; i < readBuffer.limit(); i++) {
12             System.out.print(readBuffer.get(i)+" ");
13         }

  結果:

1 0 1 2 3 4 5 6 7 8 9 0 0 0 0 0 
2 0 1 20 3 4 5 6 7 8 9 0 0 0 0 0 

  由此可見,只讀緩沖區並不是原始緩沖區在某一時刻的快照,而是和原始緩沖區共享內存數據的。當修改只讀緩沖區時,會報ReadOnlyBufferException異常。

  8、文件映射到內存:

  NIO提供了一種將文件映射到內存的方法進行I/O操作,它可以比常規的基於流的I/O快很多。這個操作主要是由FileChannel.map()方法實現的。

  使用文件映射的方式,將文本文件通過FileChannel映射到內存中。然後在內存中讀取文件內容。還可以修改Buffer,將實際數據寫到對應的硬盤中。

1      RandomAccessFile raf = new RandomAccessFile("D:\\test.txt", "rw");
2         FileChannel fc = raf.getChannel();
3         MappedByteBuffer mbf = fc.map(MapMode.READ_WRITE, 0, raf.length());//將文件映射到內存
4         while(mbf.hasRemaining()){
5             System.out.println(mbf.get());
6         }
7         mbf.put(0,(byte)98);//修改文件
8         raf.close();

  9、處理結構化數據

  NIO還提供了處理結構化數據的方法,稱為散射和聚集。散射是將一組數據讀入到一組buffer中,聚集是將數據寫入到一組buffer中。聚集和散射的基本使用方法和對單個buffer操作的使用方法類似。這一組緩沖區類似於一個大的緩沖區。

  散射/聚集IO對處理結構化數據非常有用。例如,對於一個具有固定格式的文件的讀寫,在已知文件具體結構的情況下,可以構造若干個符合文件結構的buffer,使得各個buffer的大小恰好符合文件各段結構的大小。

  例如,將"姓名:張三,年齡:18",通過聚集寫創建該文件,然後再通過散射都來解析。

 1 ByteBuffer nameBuffer = ByteBuffer.wrap("姓名:張三,".getBytes("utf-8"));
 2         ByteBuffer ageBuffer = ByteBuffer.wrap("年齡:18".getBytes("utf-8"));
 3         int nameLength = nameBuffer.limit();
 4         int ageLength = ageBuffer.limit();
 5         ByteBuffer[] bufs = new ByteBuffer[]{nameBuffer,ageBuffer};
 6         File file = new File("D:\\name.txt");
 7         if(!file.exists()){
 8             file.createNewFile();
 9         }
10         FileOutputStream fos = new FileOutputStream(file);
11         FileChannel channel = fos.getChannel();
12         channel.write(bufs);
13         channel.close();
14         
15         ByteBuffer nameBuffer2 = ByteBuffer.allocate(nameLength);
16         ByteBuffer ageBuffer2 = ByteBuffer.allocate(ageLength);
17         ByteBuffer[] bufs2 = new ByteBuffer[]{nameBuffer2,ageBuffer2};
18         FileInputStream fis = new FileInputStream("D:\\name.txt");
19         FileChannel channel2 = fis.getChannel();
20         channel2.read(bufs2);
21         String name = new String(bufs2[0].array(),"utf-8");
22         String age = new String(bufs2[1].array(),"utf-8");
23         
24         System.out.println(name+age);

  通過和通道的配合使用,可以簡化Buffer對於結構化數據處理的難度。

  注意,ByteBuffer是將文件一次性讀入內存再做處理,而Stream方式則是邊讀取文件邊處理數據,這也是兩者性能差異的主要原因。

  • 直接內存訪問

  NIO的Buffer還提供了一個可以直接訪問系統物理內存的類--DirectBuffer。普通的ByteBuffer依然在JVM堆上分配空間,其最大內存,受最大堆的限制。而DirecBuffer直接分配在物理內存中,並不占用堆空間。創建DirectBuffer的方法是:ByteBuffer.allocateDirect(capacity)。

  在對普通的ByteBuffer的訪問,系統總會使用一個"內核緩沖區"進行間接操作。而ByteBuffer所處的位置,就相當於這個"內核緩沖區"。因此,DirecBuffer是一種更加接近底層的操作。

  DirectBuffer的訪問速度遠高於ByteBuffer,但是其創建和銷毀所消耗的時間卻遠大於ByteBuffer。在需要頻繁創建和銷毀Buffer的場合,顯然不適合DirectBuffer的使用,但是如果能將DirectBuffer進行復用,那麼在讀寫頻繁的場合下,它完全可以大幅度改善系統性能。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved