背景
傳統流I/O是基於字節的,所有I/O都被視爲單個字節的移動;而NIO是基於塊的,大家可能猜到了,NIO的性能肯定優於流I/O。沒錯!其性能的提高 要得益於其使用的結構更接近操作系統執行I/O的方式:通道和緩衝器。
我們可以把它想象成一個煤礦,通道是一個包含煤層(數據)的礦藏,而緩衝器則是派送 到礦藏的卡車。卡車載滿煤炭而歸,我們再從卡車上獲得煤炭。也就是說,我們並沒有直接和通道交互;我們只是和緩衝器交互,並把緩衝器派送到通道。通道要麼 從緩衝器獲得數據,要麼向緩衝器發送數據。(這段比喻出自Java編程思想)
NIO的主要應用在高性能、高容量服務端應用程序,典型的有Apache Mina就是基於它的。
緩衝區Buffer
緩衝區實際上是一個容器對象,更直接的說,其實就是一個數組,在NIO庫中,所有數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的; 在寫入數據時,它也是寫入到緩衝區中的;任何時候訪問 NIO 中的數據,都是將它放到緩衝區中。而在面向流I/O系統中,所有數據都是直接寫入或者直接將數據讀取到Stream對象中。
在NIO中,所有的緩衝區類型都繼承於抽象類Buffer,最常用的就是ByteBuffer,對於Java中的基本類型,基本都有一個具體Buffer類型與之相對應,它們之間的繼承關係如下圖所示:
下面是一個簡單的使用IntBuffer的例子:
public class TestIntBuffer {
public static void main(String[] args) {
// 分配新的int緩衝區,參數爲緩衝區容量
// 新緩衝區的當前位置將爲零,其界限(限制位置)將爲其容量。它將具有一個底層實現數組,其數組偏移量將爲零。
IntBuffer buffer = IntBuffer.allocate(8);
for (int i = 0; i < buffer.capacity(); ++i) {
int j = 2 * (i + 1);
// 將給定整數寫入此緩衝區的當前位置,當前位置遞增
buffer.put(j);
}
// 重設此緩衝區,將限制設置爲當前位置,然後將當前位置設置爲0
buffer.flip();
// 查看在當前位置和限制位置之間是否有元素
while (buffer.hasRemaining()) {
// 讀取此緩衝區當前位置的整數,然後當前位置遞增
int j = buffer.get();
System.out.print(j + " ");
}
}
}
通道Channel
通道是一個對象,通過它可以讀取和寫入數據,當然了所有數據都通過Buffer對象來處理。我們永遠不會將字節直接寫入通道中,相反是將數據寫入包含一個或者多個字節的緩衝區。同樣不會直接從通道中讀取字節,而是將數據從通道讀入緩衝區,再從緩衝區獲取這個字節。
在NIO中,提供了多種通道對象,而所有的通道對象都實現了Channel接口。它們之間的繼承關係如下圖所示:
使用NIO讀取數據
在前面我們說過,任何時候讀取數據,都不是直接從通道讀取,而是從通道讀取到緩衝區。所以使用NIO讀取數據可以分爲下面三個步驟:
1. 從FileInputStream獲取Channel
2. 創建Buffer
1. 從FileInputStream獲取Channel
2. 創建Buffer
3. 將數據從Channel讀取到Buffer中
下面是一個簡單的使用NIO從文件中讀取數據的例子:
public class Program {
static public void main( String args[] ) throws Exception {
FileInputStream fin = new FileInputStream("c:\\test.txt");
// 獲取通道
FileChannel fc = fin.getChannel();
// 創建緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 讀取數據到緩衝區
fc.read(buffer);
buffer.flip();
while (buffer.remaining()>0) {
byte b = buffer.get();
System.out.print(((char)b));
}
fin.close();
}
}
使用NIO寫入數據
使用NIO寫入數據與讀取數據的過程類似,同樣數據不是直接寫入通道,而是寫入緩衝區,可以分爲下面三個步驟:
1. 從FileInputStream獲取Channel
2. 創建Buffer
1. 從FileInputStream獲取Channel
2. 創建Buffer
3. 將數據從Channel寫入到Buffer中
下面是一個簡單的使用NIO向文件中寫入數據的例子:
public class Program {
static private final byte message[] = { 83, 111, 109, 101, 32,
98, 121, 116, 101, 115, 46 };
static public void main( String args[] ) throws Exception {
FileOutputStream fout = new FileOutputStream( "c:\\test.txt" );
FileChannel fc = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<message.length; ++i) {
buffer.put( message[i] );
}
buffer.flip();
fc.write( buffer );
fout.close();
}
}