Java NIO(一)入門篇
概念
java.nio(java new IO),是jdk1.4 裡提供的新api ,為所有的原始型別提供快取支援。Sun 官方標榜的特性如下: 為所有的原始型別提供(Buffer)快取支援。字符集編碼解碼解決方案。 Channel :一個新的原始I/O 抽象。 支援鎖和記憶體對映檔案的檔案訪問介面。 提供多路(non-bloking) 非阻塞式的高伸縮性網路I/O。NIO和IO的主要差別
IO NIO
面向流 面向緩衝
阻塞IO 非阻塞IO
無 selector
無 channel
面向流與面向緩衝
Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java
IO面向流意味著每次從流中讀一個或多個位元組,直至讀取所有位元組,它們沒有被快取在任何地方。比如InputStream只能進行讀取操作,而OutputStream只能進行寫操作。Java NIO 提供 了channel,Channel和傳統IO中的Stream很相似。雖然很相似,但是有很大的區別,主要區別為:通道是雙向的,通過一個Channel既可以進行讀,也可以進行寫;它將資料讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的資料。而且,需確保當更多的資料讀入緩衝區時,不要覆蓋緩衝區裡尚未處理的資料(如圖)。
阻塞與非阻塞IO
Java IO的各種流是阻塞的。這意味著,當一個執行緒呼叫read()或write()時,該執行緒被阻塞,直到有一些資料被讀取,或資料完全寫入。該執行緒在此期間不能再幹任何事情了。Java
NIO的非阻塞模式,使一個執行緒從某通道傳送請求讀取資料,但是它僅能得到目前可用的資料,如果目前沒有資料可用時,就什麼都不會獲取。而不是保持執行緒阻塞,所以直至資料變的可以讀取之前,該執行緒可以繼續做其他的事情。 非阻塞寫也是如此。一個執行緒請求寫入一些資料到某通道,但不需要等待它完全寫入,這個執行緒同時可以去做別的事情。 執行緒通常將非阻塞IO的空閒時間用於在其它通道上執行
一個常見的網路 IO 通訊流程如下 :
從該網路通訊過程來理解一下何為阻塞 :
在以上過程中若連線還沒到來,那麼 accept 會阻塞 , 程式執行到這裡不得不掛起, CPU 轉而執行其他執行緒。
在以上過程中若資料還沒準備好, read 會一樣也會阻塞。
阻塞式網路 IO 的特點:多執行緒處理多個連線。每個執行緒擁有自己的棧空間並且佔用一些 CPU 時間。每個執行緒遇到外部為準備好的時候,都會阻塞掉。阻塞的結果就是會帶來大量的程序上下文切換。且大部分程序上下文切換可能是無意義的。比如假設一個執行緒監聽一個埠,一天只會有幾次請求進來,但是該 cpu 不得不為該執行緒不斷做上下文切換嘗試,大部分的切換以阻塞告終。
一個常見的網路NIO 通訊流程如下 :
把整個過程切換成小的任務,通過任務間協作完成。
由一個專門的執行緒來處理所有的 IO 事件,並負責分發。
事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。
執行緒通訊:執行緒之間通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的程序切換。
選擇器(Selectors)
Selector類是NIO的核心類,Selector能夠檢測多個註冊的通道上是否有事件發生,如果有事件發生,便獲取事件然後針對每個事件進行相應的響應處理。這樣一來,只是用一個單執行緒就可以管理多個通道,也就是管理多個連線。這樣使得只有在連線真正有讀寫事件發生時,才會呼叫函式來進行讀寫,就大大地減少了系統開銷,並且不必為每個連線都建立一個執行緒,不用去維護多個執行緒,並且避免了多執行緒之間的上下文切換導致的開銷。
與Selector有關的一個關鍵類是SelectionKey,一個SelectionKey表示一個到達的事件,這2個類構成了服務端處理業務的關鍵邏輯。
channel
在前面已經提到,Channel和傳統IO中的Stream很相似。雖然很相似,但是有很大的區別,主要區別為:通道是雙向的,通過一個Channel既可以進行讀,也可以進行寫;而Stream只能進行單向操作,通過一個Stream只能進行讀或者寫;
以下是常用的幾種通道:
- FileChannel
- SocketChanel
- ServerSocketChannel
- DatagramChannel
通過使用FileChannel可以從檔案讀或者向檔案寫入資料;通過SocketChannel,以TCP來向網路連線的兩端讀寫資料;通過ServerSocketChanel能夠監聽客戶端發起的TCP連線,併為每個TCP連線建立一個新的SocketChannel來進行資料讀寫;通過DatagramChannel,以UDP協議來向網路連線的兩端讀寫資料。
下面給出通過FileChannel來向檔案中寫入資料的一個例子:
public class Test
{
public static void main(String[]
args) throws IOException
{
File
file = new File( "data.txt" );
FileOutputStream
outputStream = new FileOutputStream(file);
FileChannel
channel = outputStream.getChannel();
ByteBuffer
buffer = ByteBuffer.allocate( 1024 );
String
string = "java
nio" ;
buffer.put(string.getBytes());
buffer.flip(); //此處必須要呼叫buffer的flip方法
channel.write(buffer);
channel.close();
outputStream.close();
}
}
通過上面的程式會向工程目錄下的data.txt檔案寫入字串"java nio",注意在呼叫channel的write方法之前必須呼叫buffer的flip方法,否則無法正確寫入內容。
|