1. 程式人生 > >Java NIO(一)入門篇

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 NIOIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java IO面向流意味著每次從流中讀一個或多個位元組,直至讀取所有位元組,它們沒有被快取在任何地方。比如InputStream只能進行讀取操作,而OutputStream只能進行寫操作。Java NIO 提供 了channel,Channel和傳統IO中的Stream很相似。雖然很相似,但是有很大的區別,主要區別為:通道是雙向的,通過一個Channel既可以進行讀,也可以進行寫;它將資料讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的資料。而且,需確保當更多的資料讀入緩衝區時,不要覆蓋緩衝區裡尚未處理的資料(如圖)。


阻塞與非阻塞IO

Java IO的各種流是阻塞的。這意味著,當一個執行緒呼叫read()write()時,該執行緒被阻塞,直到有一些資料被讀取,或資料完全寫入。該執行緒在此期間不能再幹任何事情了。Java NIO的非阻塞模式,使一個執行緒從某通道傳送請求讀取資料,但是它僅能得到目前可用的資料,如果目前沒有資料可用時,就什麼都不會獲取。而不是保持執行緒阻塞,所以直至資料變的可以讀取之前,該執行緒可以繼續做其他的事情。 非阻塞寫也是如此。一個執行緒請求寫入一些資料到某通道,但不需要等待它完全寫入,這個執行緒同時可以去做別的事情。 執行緒通常將非阻塞IO的空閒時間用於在其它通道上執行

IO操作,所以一個單獨的執行緒現在可以管理多個輸入和輸出通道(channel)。


一個常見的網路 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方法,否則無法正確寫入內容。