java NIO 就是NEW I O,他與傳統IO的最大的區別是 它是非阻塞IO。
Java NIO和IO之間的主要差別:
IO NIO 面向流 面向緩衝 阻塞IO 非阻塞IO 無 選擇器 |
他們各自適用於不同的環境,這裏只簡單的說明其區別,具體的見博客:
http://ifeve.com/java-nio-vs-io/
NIO的核心api 是 Channel、Buffer和Selector,本品文章着重介紹前兩種。
一個簡單例子
首先舉一個簡單的例子進行說明,這個例子使用NIO的api向一個文件中寫入數據,然後把他讀出來打印,此例子的註釋很詳細,可細細品味,另外有關NIO的概述可見譯文:
/**
* NIO 簡單測試例子,向一個文件中寫入數據,然後把它讀出來。這個例子
* 主要說明 NIO 和傳統IO的不同,需要注意channel 和 buffer的使用。
* 理解 NIO 是面向緩衝的。而IO是面向流的。
*
* 以下例子要理解NIO的使用 過程,1 定義一個 channel , 2,定義一個buffer
* 3 向buffer寫數據 (寫的含義有兩種,與channel有關) 4 轉換爲讀模式 5.讀取buffer數據 (讀的含義與channel的方向有關)
*
*/
public class Main {
public static void main(String[] args) throws IOException {
File a = new File("test.txt");
if (!a.exists()) {
a.createNewFile();
}
FileOutputStream out = new FileOutputStream(a);
//構造輸出channel,這裏使用filechannel
FileChannel outChannel = out.getChannel();
String testStr = "Lina , i love you";
//構造一個輸出的緩存。
ByteBuffer byteBuffer1 = ByteBuffer.allocate(512);
//put方法向緩存中寫數據,注意是寫
byteBuffer1.put(testStr.getBytes());
//下面的三行代碼將向文件中寫數據,filp()方法把buffer轉換爲讀模式,然後使用channel的寫方法寫數據到文件。
//注意的是,outChannel 寫入文件的數據是從charbuffer“讀取”的, 寫入或者讀取 是針對buffer而言,不是根據channel說的,這點需要理解
byteBuffer1.flip();
while (byteBuffer1.hasRemaining()) {
outChannel.write(byteBuffer1);
}
outChannel.close();
out.close();
FileInputStream in = new FileInputStream(a);
//構造輸入channel和 buffer,跟以上對比,channel的方向是 底層的包裝層決定的。
FileChannel inChannel = in.getChannel();
ByteBuffer inBuffer = ByteBuffer.allocate(512);
//循環讀取文件數據,寫入到 buffer中,注意這裏還是寫入buffer
while (inChannel.read(inBuffer)!=-1) {
}
//轉換爲讀模式,flip方法調用後,buffer會發生一些變化,這裏暫不討論。
inBuffer.flip();
byte[] dest = new byte[inBuffer.limit()];
//讀取數據到byte數組。
inBuffer.get(dest, 0, inBuffer.limit());
inChannel.close();
in.close();
System.out.println(new String(dest));
}
Channel
官方文檔定義channel指“與一個實體的連接”,這個實體可以是一個設備、文件、socket等。channel的接口定義本身簡單,JDK提供了幾種實現。
public interface Channel extends Closeable {
public boolean isOpen();
public void close() throws IOException;
}
上圖展示了channel的體系,可見WritableByteChannel和ReadableByteChannel 分別擴展了channel接口,加入了讀和寫的功能,其中最常見的實現已經分別用紅色框起來,他們分別適用於不同的場合。
FileChannel 從文件中讀寫數據,與fileinput/outputStream對應
DatagramChannel 能通過UDP讀寫網絡中的數據。對應DatagramSocket
SocketChannel 能通過TCP讀寫網絡中的數據。對應IO中的socket
ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel。對應IO中ServerSocket
Buffer
首先貼一張圖來表示Channel和Buffer的關係,即channel是通過buffer來讀寫數據的。關於buffer,http://ifeve.com/buffers/ ,以上地址翻譯的很好,這裏摘出裏面的部分內容。
基本用法
簡單的例子中,已經說明了channel和buffer的基本用法這裏,做個簡單的總結,
使用Buffer讀寫數據一般遵循以下四個步驟:
1. 寫入數據到Buffer
2. 調用flip()方法
3. 從Buffer中讀取數據
4. 調用clear()方法或者compact()方法
當向buffer寫入數據時,buffer會記錄下寫了多少數據。一旦要讀取數據,需要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到buffer的所有數據。
一旦讀完了所有的數據,就需要清空緩衝區,讓它可以再次被寫入。有兩種方式能清空緩衝區:調用clear()或compact()方法。clear()方法會清空整個緩衝區。compact()方法只會清除已經讀過的數據。任何未讀的數據都被移到緩衝區的起始處,新寫入的數據將放到緩衝區未讀數據的後面。
Buffer的屬性
buffer有三個基本的屬性capacity limit position.
capacity 是指buffer的大小,在buffer建立的時候已經確定。
limit 當buffer處於寫模式,指還可以寫入多少數據,處於讀模式,指還有多少數據可以讀。
position 當buffer處於寫模式,指下一個寫數據的位置, 處於讀模式,當前將要讀取的數據的位置。每讀寫一個數據,position+1
也就是 limit 和position在 buffer的讀/寫時的含義不一樣。當調用buffer的flip方法,由寫模式變爲讀模式時,
limit(讀)=position(寫)
position(讀) =0;
Buffer的類型
buffer有多種類型,不同的buffer提供不同的方式操作buffer中的數據。
buffer的操作
寫數據
寫數據到buffer有兩種情況:
1. 從channel寫到 buffer,如例子中channel從文件中讀取數據,寫到channel
2. 直接調用put方法,往裏面寫數據
flip()
這個操作已經解釋過,轉換buffer爲讀模式
讀數據
從Buffer中讀取數據有兩種方式:
1. 從Buffer讀取數據到Channel。
2. 使用get()方法從Buffer中讀取數據。
rewind()
Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
clear() 和 compact()方法
一旦讀完Buffer中的數據,需要讓Buffer準備好再次被寫入。可以通過clear()或compact()方法來完成。
如果調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數據並未清除,只是這些標記告訴我們可以從哪裏開始往Buffer裏寫數據。
如果Buffer中有一些未讀的數據,調用clear()方法,數據將“被遺忘”,意味着不再有任何標記會告訴你哪些數據被讀過,哪些還沒有。
如果Buffer中仍有未讀的數據,且後續還需要這些數據,但是此時想要先先寫些數據,那麼使用compact()方法。
compact()方法將所有未讀的數據拷貝到Buffer起始處。然後將position設到最後一個未讀元素正後面。limit屬性依然像clear()方法一樣,設置成capacity。現在Buffer準備好寫數據了,但是不會覆蓋未讀的數據。