Java NIO入門學習(一)

【轉載】: https://blog.csdn.net/hellohm/article/details/21508981

本文爲NIO入門學習的第一篇,將會介紹NIO中幾個重要的概念。

I/O即輸入輸出,指的是計算機和外界的接口,或者是單個程序同計算機其他部分的接口。 在Java1.4之前的I/O系統中,提供的都是面向流的I/O系統,系統每次處理一個字節,輸入流(input stream)生產一個字節,輸出流(output stream)消費一個字節。這種工作模式下,非常容易給流數據創建過濾器(filters),而且也很容易將多個過濾器串起來,每個過濾器針對流過自己的字節做相應處理。另一方面,在這種工作模式下面向流的IO通常很慢。而在Java 1.4中推出了NIO(New I/O),這是一個面向塊的I/O系統,系統以塊爲單位處理數據,每個操作都會生產或者消費一“塊”數據,以塊爲單位處理數據會比以字節(流)爲單位處理數據快很多。但是面向塊的IO系統同時也損失了一些優雅而簡單的操作方式。 

在NIO中有幾個核心對象需要掌握:緩衝區(Buffer)、通道(Channel)、選擇器(Selector)。


緩衝區Buffer

Buffer本質上說是一個容器對象。任何發送到Channel的數據都必須先放進Buffer,類似的,任何從Channel中讀出的數據都先讀進Buffer。

Buffer就是一個裝載數據的容器對象,數據從Buffer中讀出,或者把數據寫入Buffer中。在NIO中添加了Buffer對象,這是NIO和老IO最重要的區別。在面向流的I/O中,你可以把數據直接寫入Stream對象,或者直接把數據從Stream中讀出來,而不需要任何容器。 

Buffer本質上就是一個數組(array)。通常它是一個字節數組,或者其他種類的可用數組。但是Buffer除了是一個數組之外,它還提供了結構化的訪問數據的方法,並且還用來跟蹤系統的讀/寫過程。 

在NIO中,所有的緩衝區類型都繼承於抽象類Buffer,最常用的就是ByteBuffer。對於Java中的基本數據類型,基本上都有一個具體Buffer類型與之相對應,它們之間的繼承關係如下圖所示:


下面是一個簡單的使用IntBuffer的例子:

[java] view plain copy
  1. import java.nio.IntBuffer;  
  2.   
  3. public class TestIntBuffer {  
  4.   
  5.     public static void main(String[] args) {  
  6.         // 分配新的int緩衝區,參數爲緩衝區容量  
  7.         // 新緩衝區的當前位置position將爲零,其界限(限制位置)limit將爲其容量。  
  8.         // 它將具有一個底層實現數組,其數組偏移量將爲零。  
  9.         IntBuffer buffer = IntBuffer.allocate(8);  
  10.   
  11.         for (int i = 0; i < buffer.capacity(); ++i) {  
  12.             int j = 2 * (i + 1);  
  13.             // 將給定整數寫入buffer的當前位置  
  14.             buffer.put(j);  
  15.         }  
  16.   
  17.         // 重設buffer,將limit設置爲position,position設置爲0  
  18.         buffer.flip();  
  19.   
  20.         // 查看在position和limit之間是否有元素  
  21.         while (buffer.hasRemaining()) {  
  22.             // 讀取buffer當前位置的整數  
  23.             int j = buffer.get();  
  24.             System.out.print(j + " ");  
  25.         }  
  26.     }  
  27.   
  28. }  

運行後可以看到:


在後面我們還會繼續分析Buffer對象,以及它的幾個重要的屬性。


通道Channel

Channel模擬了老IO包中的流的概念。所有去任何地方(或者來自任何地方)的數據都必須通過Channel對象。可以從Channel中讀取數據,也可以從Channel中寫入數據。NIO與老IO相比而言,Channel就如同Stream。 

所有被NIO處理的數據都必須通過Buffer對象。不能直接將任何字節寫入Channel,而是必須先將數據寫入Buffer。同樣的,也不能直接從Channel中讀取任何字節,必須先通過Channel將數據讀入Buffer,然後再從Buffer中獲取數據。 

Channel與Stream的區別在於:Channel是雙向的,而Stream只能是單向的(Stream必須是InputStream或者OutputStream的一個子類,即要麼是輸入流,要麼是輸出流,不能即輸入又輸出)。Channel在被打開之後,即可以讀,也可以寫,或者同時進行讀寫操作。 因爲Channel是雙向的,因此它比Stream更好的反應了底層操作系統IO的實質。特別是在Linux系統中,底層操作系統都是雙向的。

在NIO中,提供了多種通道對象,而所有的通道對象都實現了Channel接口。它們之間的繼承關係如下圖所示:



使用NIO讀取數據

在第一個練習中,我們首先從文件中讀取一些數據。如果使用老的IO,只需要簡單的創建FileInputStream,然後從中讀取數據。在NIO中,事情變得有些不同了,首先需要從FileInputStream中獲取Channel對象,然後使用這個Channel去讀文件。 

在NIO系統中任何時刻執行一個讀操作時,都要從Buffer中讀,但不是直接從Channel中讀。由於所有的數據都需要通過Buffer承載,所以需首先從Channel中把數據讀進Buffer。

因此,從文件中讀數據一共有三步:

1. 從FileInputStream中獲取Channel

2. 創建Buffer

3. 從Channel中把數據讀入Buffer

下面是一個簡單的使用NIO從文件中讀取數據的例子:

[java] view plain copy
  1. import java.io.FileInputStream;  
  2. import java.nio.ByteBuffer;  
  3. import java.nio.channels.FileChannel;  
  4.   
  5. public class TestChannelRead {  
  6.   
  7.     public static void main(String[] args) throws Exception {  
  8.         FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");  
  9.         // 獲取通道  
  10.         FileChannel fileChannel = fileInputStream.getChannel();  
  11.   
  12.         // 創建緩衝區  
  13.         ByteBuffer buffer = ByteBuffer.allocate(1024);  
  14.   
  15.         // 讀取數據到緩衝區  
  16.         fileChannel.read(buffer);  
  17.   
  18.         // 重設buffer,將limit設置爲position,position設置爲0  
  19.         buffer.flip();  
  20.   
  21.         // 查看在position和limit之間是否有元素  
  22.         while (buffer.hasRemaining()) {  
  23.             // 讀取buffer當前位置的整數  
  24.             byte b = buffer.get();  
  25.             System.out.print((char) b);  
  26.         }  
  27.   
  28.         fileInputStream.close();  
  29.     }  
  30.   
  31. }  
你一定發現,在這裏,並沒有告訴Channel把多少內容讀進Buffer。在每個Buffer中都有一套完整的內部計數系統來跟蹤已經讀了多少數據了,Buffer中還剩多少空間。關於Buffer的計數系統在隨後的“Buffer內部原理”中講解。


使用NIO寫入數據

使用NIO寫入數據與讀取數據的過程類似,同樣數據不是直接寫入Channel,而是先將數據寫入Buffer,可以分爲下面三個步驟:

1. 從FileOutputStream獲取Channel

2. 創建Buffer並且把數據放到Buffer中

3. 將Buffer中的數據寫入Channel

下面是一個簡單的使用NIO向文件中寫入數據的例子:

[java] view plain copy
  1. import java.io.FileOutputStream;  
  2. import java.nio.ByteBuffer;  
  3. import java.nio.channels.FileChannel;  
  4.   
  5. public class TestChannelWrite {  
  6.   
  7.     private static byte message[] = { 831111091013298121116101,  
  8.             11546 };  
  9.   
  10.     public static void main(String[] args) throws Exception {  
  11.         FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");  
  12.         // 獲取通道  
  13.         FileChannel fileChannel = fileOutputStream.getChannel();  
  14.   
  15.         // 創建緩衝區  
  16.         ByteBuffer buffer = ByteBuffer.allocate(1024);  
  17.         // 數據存入緩衝區  
  18.         for (int i = 0; i < message.length; ++i) {  
  19.             buffer.put(message[i]);  
  20.         }  
  21.         // 重設buffer,將limit設置爲position,position設置爲0  
  22.         buffer.flip();  
  23.   
  24.         // 將buffer中的數據寫入  
  25.         fileChannel.write(buffer);  
  26.   
  27.         fileOutputStream.close();  
  28.     }  
  29.   
  30. }  
再次注意,我們沒有必要告訴Channel總共要寫多少數據,Buffer的內部計數系統會跟蹤已經寫了多少數據了,還剩多少空間可以使用。


本文介紹了Java NIO中三個核心概念中的兩個,並且看了兩個簡單的示例,分別是使用NIO進行數據的讀取和寫入,下篇將會介紹Buffer內部實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章