本文爲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的例子:
- import java.nio.IntBuffer;
- public class TestIntBuffer {
- public static void main(String[] args) {
- // 分配新的int緩衝區,參數爲緩衝區容量
- // 新緩衝區的當前位置position將爲零,其界限(限制位置)limit將爲其容量。
- // 它將具有一個底層實現數組,其數組偏移量將爲零。
- IntBuffer buffer = IntBuffer.allocate(8);
- for (int i = 0; i < buffer.capacity(); ++i) {
- int j = 2 * (i + 1);
- // 將給定整數寫入buffer的當前位置
- buffer.put(j);
- }
- // 重設buffer,將limit設置爲position,position設置爲0
- buffer.flip();
- // 查看在position和limit之間是否有元素
- while (buffer.hasRemaining()) {
- // 讀取buffer當前位置的整數
- int j = buffer.get();
- System.out.print(j + " ");
- }
- }
- }
運行後可以看到:
在後面我們還會繼續分析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從文件中讀取數據的例子:
- import java.io.FileInputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class TestChannelRead {
- public static void main(String[] args) throws Exception {
- FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
- // 獲取通道
- FileChannel fileChannel = fileInputStream.getChannel();
- // 創建緩衝區
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- // 讀取數據到緩衝區
- fileChannel.read(buffer);
- // 重設buffer,將limit設置爲position,position設置爲0
- buffer.flip();
- // 查看在position和limit之間是否有元素
- while (buffer.hasRemaining()) {
- // 讀取buffer當前位置的整數
- byte b = buffer.get();
- System.out.print((char) b);
- }
- fileInputStream.close();
- }
- }
使用NIO寫入數據
使用NIO寫入數據與讀取數據的過程類似,同樣數據不是直接寫入Channel,而是先將數據寫入Buffer,可以分爲下面三個步驟:
1. 從FileOutputStream獲取Channel
2. 創建Buffer並且把數據放到Buffer中
3. 將Buffer中的數據寫入Channel
下面是一個簡單的使用NIO向文件中寫入數據的例子:
- import java.io.FileOutputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.FileChannel;
- public class TestChannelWrite {
- private static byte message[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101,
- 115, 46 };
- public static void main(String[] args) throws Exception {
- FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");
- // 獲取通道
- FileChannel fileChannel = fileOutputStream.getChannel();
- // 創建緩衝區
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- // 數據存入緩衝區
- for (int i = 0; i < message.length; ++i) {
- buffer.put(message[i]);
- }
- // 重設buffer,將limit設置爲position,position設置爲0
- buffer.flip();
- // 將buffer中的數據寫入
- fileChannel.write(buffer);
- fileOutputStream.close();
- }
- }
本文介紹了Java NIO中三個核心概念中的兩個,並且看了兩個簡單的示例,分別是使用NIO進行數據的讀取和寫入,下篇將會介紹Buffer內部實現。