1. 程式人生 > >BIO--NIO-AIO(IOCP在Java7中的實現)

BIO--NIO-AIO(IOCP在Java7中的實現)

背景

   在 Java 的早期,JVM 在解釋位元組碼時往往很少或沒有執行時優化。這就意味著,Java 程式往往拖得很長,其執行速率大大低於本地編譯程式碼,因而對作業系統I/O 子系統的要求並不太高。 如今在執行時優化方面,JVM 已然前進了一大步。現在 JVM 執行位元組碼的速率已經接近本地編譯程式碼,藉助動態執行時優化,其表現甚至還有所超越。這就意味著,多數 Java 應用程式已不再受 CPU 的束縛(把大量時間用在執行程式碼上),而更多時候是受 I/O 的束縛(等待資料傳輸)。  


     然而,在大多數情況下,Java 應用程式並非真的受著 I/O 的束縛。作業系統並非不能快速傳送資料,讓 Java 有事可做;相反,是 JVM 自身在 I/O 方面效率欠佳
。 
作業系統與 Java 基於流的 I/O模型有些不匹配。作業系統要移動的是大塊資料(緩衝區),這往往是在硬體直接儲存器存取(DMA)的協助下完成的。而 JVM 的 I/O 類喜歡操作小塊資料——單個位元組、幾行文字。結果,作業系統送來整緩衝區的資料,java.io 的流資料類再花大量時間把它們拆成小塊,往往拷貝一個小塊就要往返於幾層物件。作業系統喜歡整卡車地運來資料,java.io 類則喜歡一鏟子一鏟子地加工資料。有了 NIO,就可以輕鬆地把一卡車資料備份到您能直接使用的地方(ByteBuffer 物件)。  

一  流IO---塊IO的區別

1)面向流I/O的系統:

    一次處理一個位元組的資料。一個輸入流每次會讀入一個位元組的資料,一個輸出流同樣每次次消費一個位元組的資料。

對於流式資料,很容易建立過濾器。可以相對簡單地把幾個過濾器連線在一起,每個過濾器完成自己的工作,也是按位元組進行過濾,精細的處理機制。另一方面,面向流I/-O的通訊往往比較緩慢。  

2)面向塊I/O的系統:

    以塊為單位處理資料。每個操作步驟會生成或消費一個塊的資料。以塊為單位處理資料,其處理速度遠快於以位元組流為單位的方式。但是,與面向流I/O的通訊相比,面向塊I/O的通訊缺乏優雅和簡潔。  

     新的抽象把重點放在瞭如何縮短抽象與現實之間的距離上面。NIO 抽象與現實中存在的實體有著非常真實直接的互動關係。  

關於何時該採用傳統io,何時應該採用nio:  

1) 擴充套件性考慮:

     例如在進行Socket程式設計通訊時每一個Socket都應占據一個執行緒。使用NIO雖然更富有效率,但相對難以編碼和擴充套件。(當然這一現象在不斷的被新的設計和NIO庫的特性所改善)  

2) 效能考慮:

     在處理成千上萬的連線時,你可能需要更好的傳統IO的擴充套件性;但是如果連線數量較低時,你可能更注重NIO的高吞吐率。  

3) 當使用SSL (Secure Sockets Layer,安全套接字層) 工作時,

      選擇NIO則實現難度很大  

                      

二  IOCP

2.1什麼是 IOCP

       隨著計算機技術,尤其是網路技術的飛速發展,如今的程式開發人員不僅僅侷限於基於單機執行或單一執行緒的應用程式的開發。伺服器端 / 客戶端模式的最顯著的特點是一個伺服器端應用能同時為多個客戶端提供服務。而傳統的伺服器端 / 客戶端模式通常為每一個客戶端建立一個獨立的執行緒,這種實現方式在客戶端數量不多的情況下問題不大,但對於類似於鐵路網路訂票這樣的瞬間客戶數量巨大的系統來說,效率極端低下。這是因為一方面建立新執行緒作業系統開銷較大,另一方面同時有許多執行緒處於執行狀態,作業系統核心需要花費大量時間進行上下文切換,並沒有線上程執行上花更多的時間。        因此,微軟在 Winsocket2 中引入了 IOCP(Input/Output Completion Port)模型。IOCP 是 Input/Output Completion Port(I/O 完成埠)的簡稱。簡單的說,IOCP 是一種高效能的 I/O 模型,是一種應用程式使用執行緒池處理非同步 I/O 請求的機制。Java7 中對 IOCP 有了很好的封裝,程式設計師可以非常方便的時候經過封裝的 channel 類來讀寫和傳輸資料。

2.2同步 / 非同步,阻塞 / 非阻塞


     所謂同步,就是在發出一個功能呼叫時,在沒有得到結果之前,該呼叫就不返回。按照這個定義,其實絕大多數函式或方法都是同步呼叫。
非同步的概念和同步相對。當一個非同步過程呼叫發出後,呼叫者不能立刻得到結果。實際處理這個呼叫的部件在完成後,通過狀態、通知和回撥來通知呼叫者。
通俗來講,完成一件事再去做另外一件事就是同步,而一起做兩件或者兩件以上的事情就是非同步了。
拿一個伺服器與客戶端通訊的例子來說。
如果是同步:
   Client 傳送一條請求訊息給 Server,這個時候 Client 就會等待 Server 處理該請求。這段時間內 Client 只有等待直到 Server 回覆響應資訊給 Client。Client 只
有收到該響應資訊後,才能發起第二條請求訊息。這樣無疑大大降低了系統的效能。

而如果是非同步:

    Client 傳送一條請求訊息給 Server,Client 並不等待 Server 的處理結果,而是繼續傳送第二條甚至更多的請求訊息。Server 會將這些請求都存入佇列,逐條處理,並將處理完的結果回覆給 Client。這樣一來,Client 就可以不用等待,效率大大提高。阻塞呼叫

   是指呼叫結果返回之前,當前執行緒會被掛起。函式或方法只有在得到結果之後才會返回。阻塞和同步有點類似,但是同步呼叫的時候執行緒還是處於啟用狀態,而
阻塞時執行緒會被掛起。
非阻塞呼叫和阻塞的概念相對應,指在不能立刻得到結果之前,該函式或方法不會阻塞當前執行緒而是立刻返回。


清單 1. 傳統的網路應用程式碼

 try { 
ServerSocket server = new ServerSocket(9080); 
while (true) { 
Socket client = server.accept(); 
new Thread(new SocketHandle(client)).start(); 

 } catch (IOException e) { 
e.printStackTrace(); 
 } 


          相信只要寫過網路應用程式的朋友,應該對這樣的結構再熟悉不過了。Accept 後執行緒被掛起,等待一個客戶發出請求,而後建立新執行緒來處理請求。當新執行緒處理客戶
請求時,起初的執行緒迴圈回去等待另個客戶請求。在這個併發模型中,對每個客戶都建立了一個執行緒。其優點在於等待請求的執行緒只需要做很少的工作,而大部分的時間,該執行緒在休眠,因為 recv 處於阻塞狀態。如前文所述,建立執行緒的開銷遠遠大於程式設計師的預計,尤其是在併發量巨大的情況下,這種傳統的併發模型效率極端低下。解決這個問題的方法之一就是 IOCP,說白了 IOCP 就是一個訊息佇列。我們設想一下,如果事先開好 N 個執行緒,讓它們 hold 住,將所有使用者的請求都投遞到一個訊息佇列中去。讓後這 N 個執行緒逐一從訊息佇列中去取出訊息並加以處理。這樣一來,就可以避免對沒有使用者請求都開新執行緒,不僅減少了執行緒的資源,也提高了執行緒的利用率。

2.3IOCP 實現的基本步驟

那麼 IOCP 完成埠模型又是怎樣實現的呢?首先我們建立一個完成埠 CreateIOCompletionPort,然後再建立一個或多個工作執行緒,並指定它們到這個完成埠上去讀取資料。再將遠端連線的套接字控制代碼關聯到這個完成埠。工作執行緒呼叫 getQueuedCompletionStatus 方法在關聯到這個完成埠上的所有套接字上等待 I/O 的完成,再判斷完成了什麼型別的 I/O,然後接著發出 WSASend 和 WSARecv,並繼續下一次迴圈阻塞在 getQueuedCompletionStatus。具體的說,一個完成埠大概的處理流程包括:
建立一個完成埠;

Port port = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, fixedThreadCount());


建立一個執行緒 ThreadA

ThreadA 執行緒迴圈呼叫 GetQueuedCompletionStatus 方法來得到 I/O 操作結果,這個方法是一個阻塞方法;
 While(true){ 
getQueuedCompletionStatus(port, ioResult); 
 } 

主執行緒迴圈呼叫 accept 等待客戶端連線上來;
主執行緒 accept 返回新連線建立以後,把這個新的套接字控制代碼用 CreateIoCompletionPort 關聯到完成埠,然後發出一個非同步的 Read 或者 Write 呼叫,因為是異
函式,Read/Write 會馬上返回,實際的傳送或者接收資料的操作由作業系統去做。
 if (handle != 0L) { 
createIoCompletionPort(handle, port, key, 0); 
 } 

主執行緒繼續下一次迴圈,阻塞在 accept 這裡等待客戶端連線。
作業系統完成 Read 或者 Write 的操作,把結果發到完成埠。
          ThreadA 執行緒裡的 GetQueuedCompletionStatus() 馬上返回,並從完成埠取得剛完成的 Read/Write 的結果。
在 ThreadA 執行緒裡對這些資料進行處理 ( 如果處理過程很耗時,需要新開執行緒處理 ),然後接著發出 Read/Write,並繼續下一次迴圈阻塞在 
GetQueuedCompletionStatus() 這裡。

2.4 Java 中非同步 I/O 和 IOCP

其實,JDK 中的 IOCP 的實現與前文中所描述的實現思想基本相同,唯一的區別是 JDK 中封裝了相關的內部實現。
Iocp 類:該類使用預設的訪問修飾符,因此只能被同包(sun.nio.ch)的其他類訪問。同時,建立 IOCP 埠的 createIoCompletionPort 方法也被封裝在 Iocp 類的建構函式中,套接字的關聯也不再需要呼叫 createIoCompletionPort 而是直接呼叫 Iocp 類的 associate 方法。
Iocp.CompletionStatus 類:Iocp 的內部類 CompletionStatus 用於存取完成埠的狀態資訊。
Iocp.EventHandlerTask 類:Iocp 的內部類 EventHandlerTask 用來將 I/O 操作的 Handle 關聯到完成埠,迴圈的獲取完成埠的狀態,根據作業系統返回的完成端
口狀態從任務佇列中提取下一個要執行的任務並呼叫執行。Iocp 的呼叫類:sun.nio.ch 包中的 WindowsAsynchronousSocketChannelImpl、WindowsAsynchronousServerSocketChannelImpl 和 WindowsAsynchronousFileChannelImpl 等類使用了 Iocp 類的相關方法,達到多併發量時的高效能 I/O 訪問。Native 方法:Iocp 類中的許多內部方法都是通過呼叫 JNI 方法實現的。

例項分析
從上節描述中,您或許會意識到,Java7 中對 IOCP 進行了很好的封裝。我們可以直接使用 JDK 中眾多的 ChannelImpl 類,這些類的讀寫操作都應用了 Iocp 類。當然,您也可以將自定義的邏輯實現類放置在 sun.nio.ch 包中以重用 Iocp 類,甚至還可以自己編寫和實現 Iocp 類。本節中,我們通過一個 Log 傳輸的具體例項來看 
Iocp 以及應用類在實際應用中的使用。
         假設現在有建立了連線的伺服器端和客戶端,伺服器端執行得到的大量 Log 資訊需要傳輸到客戶端的 console 中。這些資訊的傳輸必須是非同步的,也就是說伺服器端不
等待傳輸的完成而是繼續執行併產生更多的 Log 資訊。同時,Log 資訊在 channel 上傳輸的時候又必須是同步的,這樣才能保證 Log 資訊的時間次序保持一致。首先,伺服器端監聽埠,建立與客戶端通訊的 channel。


清單 2. 建立 channel

 final AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(); 
 server.bind(new InetSocketAddress(port)); 
 server.accept(
   new Session(null), new CompletionHandler<AsynchronousSocketChannel, Session>() 
   {
public void completed(AsynchronousSocketChannel ch,Session session) { 
Session s = new Session(ch); 
server.accept(s, this); 



public void failed(Throwable e, Session session) { 

 }); 


伺服器端主執行緒並不直接將 Log 資訊通過 channel 輸出到客戶端的 console 上。而是呼叫 writeMessage 方法將執行得到的 log 資訊寫入事先準備好的 messageQueue 裡,並繼續業務邏輯的執行。


清單 3. 寫入 log 到 Queue
public synchronized void flush() throws IOException {
int size = buffer.position();
Message msg = new Message(this.msgType, new byte[size]);
buffer.rewind();
final Object mutex = new Object();
buffer.get(msg.body);
buffer.rewind();
session.writeMsg(msg);
}


Public void writeMessage(Message msg) {
synchronized(messageQueue) {
messageQueue.offer(msg);
}
}


同時,另一個執行緒負責不斷從 messageQueue 中讀取 Log 資訊,順序寫到 channel 上傳輸給客戶端 console。WriteCompletionHandler 是 CompletionHandler 介面的自定義實現類,用來控制每次 channel 上的寫操作必須完成才返回,避免混淆輸出的 Log 次序。


清單 4. 從 Queue 中讀取並傳輸 Log 至客戶端
Thread thread=new Thread(new Runnable(){
    public void run(){
        Message msg = null;
        synchronized(messageQueue){
            msg = messageQueue.poll();
            ByteBuffer byteBuffer = msg.pasteByteBuffer();
            synchronized(canWrite){
                channel.write(byteBuffer, new WriteCompletionHandler(msg));
            }
        }
    }
}).start();


       這樣一來,我們能很輕鬆的使用 channelImpl 類來網路應用,而且如果您深入到這些 channelImpl 類的內部去,您就會看到許多邏輯的實現都依賴了底層的 sun.nio.ch.Iocp 類。

三   對比Java.nio 和 Java.io 

java.io 概覽

這個包通過資料流和序列化機制來實現系統輸入和輸出。並且支援多種型別的資料流,包括簡單的位元組、原生資料型別、地區字元以及物件。流是一個數據的序列:一個程式使用輸入流從一個源頭讀取資料;一次處理一個位元組的資料。一個輸入流每次會讀入一個位元組的資料,一個輸出流同樣每次次消費一個位元組的資料

    

另一個程式使用輸出流寫入併發送資料到目的地。

   

這些程式都使用位元組流來執行位元組的輸入和輸出。所有涉及位元組流的類都是繼承自InputStream和OutputStream。

關於 InputStream 和 OutputStream

 執行InputStream和OutputStream的操作一般都意味著不斷迴圈的將位元組逐一從輸入流讀出或寫入到輸出流。你可以使用緩衝I/O流降低I/O成本凡是I/O請求都經常觸發磁碟訪問、網路動作或其他一些成本昂貴的操作)。緩衝輸入流則是從緩衝的記憶體區域讀取資料,只有緩衝讀完才會呼叫native input API(不同作業系統提供的本地輸入流API——譯者注)。同樣的,緩衝輸出流也將資料寫入緩衝,只有緩衝寫滿才會呼叫native output API。這些帶緩衝的API很好的封裝了未緩衝的流操作: BufferedInputStream 和 BufferedOutputStream.

File I/O

上面一節主要是針對資料流,它提供一種資料讀取和寫入的簡單模型。真實的資料流其實是涉及種類繁多的資料來源和目的地,包括磁碟檔案。但是,資料流並不支援所有磁碟檔案操作。下面的連結介紹了非資料流的檔案I/O:

  • File 類可以編寫平臺無關的檢查和處理檔案、目錄的程式碼。

java.net socket

兩個在網路上執行的程式之間會建立雙向通訊的連結,socket就是其中一個端點。Socket相關的類代表著客戶端程式和服務端程式之間的連線。java.net包提供了兩個類:Socket和ServerSocket。它們分別實現了連線的客戶端和服務端。

客戶端知道服務端執行機器的域名,以及伺服器監聽的埠,它嘗試連線到伺服器,如果一切正常,伺服器接受並建立連線。當接受連線時,伺服器在監聽埠上繫結一個新的socket,並且通知遠端端點設定客戶端的地址和埠。之所以要建立一個新的socket是為了處理已連線客戶端請求的同時還能繼續監聽原始socket上的連線請求。

伺服器使用阻塞模式等待客戶端連線:serverSocket.accept()是一個阻塞指令,當伺服器等待接受連線時主執行緒不能做任何其他操作。由於這個原因,伺服器想要達到多工處理就只能通過實現一個多執行緒伺服器:每當新建一個socket時就必須為它建立一個新執行緒。

NIO API

    I/O效能經常是一個現代應用的痛處。作業系統持續優化改進I/O效能,JVM也提供了一套執行環境幫助Java程式設計師規避了絕大多數作業系統I/O之間的差異。這些都讓I/O相關編碼更加高效和簡單,但是卻隱藏了作業系統的功能特性。想要增強I/O效能,其實你可以通過一些特殊的編碼直接訪問作業系統的底層功能,但是這並不是最佳解決方案——你的程式碼將必須依賴某個作業系統。 Java.nio包應運而生來解決這個難題,它提供了高效能的I/O特性,並支援當今大多數常用的商用作業系統。

JDK1.4的NIO包介紹了一系列新的I/O操作的抽象概念。

java.nio 概覽

Java.nio這個新增的包實現了Java平臺新的 I/O API。NIO API 包含如下特性:

  • 原生型別資料緩衝buffer  (Buffer只能與Channel通訊)
  • 字符集的編碼器和解碼器
  • 基於Perl風格正則表示式的模式匹配
  • 通道(Channel),一種新的原生I/O抽象概念
  • 支援鎖和記憶體對映的檔案介面
  • 通過多路複用、非阻塞的I/O能力實現可伸縮的伺服器架構

在SUN(現Oracle)的站點上可以找到java.nio的詳細技術文件。這裡我將解釋一些nio的概念,並且和老的java.io庫做下比較。建議不要把java.nio當作java.io的替代品,即使它是java.io的“擴充套件”。Nio的誕生導致了整個I/O類和介面的重新修訂(詳情請看)。
NIO中一個最重要的概念是在非阻塞模式下執行,與傳統Java I/O類庫完全不同。什麼是非阻塞模式?

Non blocking mode 非阻塞模式

  一個I/O流的位元組必須序列化的訪問。各種裝置,印表機埠、網路連線等都是常見的例子。
資料流通常比阻塞式裝置慢,而且經常斷斷續續。大多數作業系統允許將資料流設定為非阻塞模式,允許程序檢查是否流上是否有可用資料,即使沒有也不會導致程序阻塞。這種機制能讓程序在輸入流空閒等待時執行其他邏輯,但是資料到達時又能及時處理。作業系統能夠觀察一堆的資料流並且指示其中哪些處於可用狀態。通過觀察這些可用狀態,一個程序可以採用常用的編碼和單個執行緒就能同時處理多個活動的資料流。這在網路伺服器處理大量網路連線時被廣泛使用。

Buffers 緩衝

從簡單的入手,首先的改進是一系列java.io包中新建的緩衝類。這些緩衝提供了一個可以在記憶體容器中儲存一堆原生型別資料的機制。一個緩衝物件是一個固定容量的容器,容器中的資料可以被讀寫。

 所有的緩衝都是可讀的,但並非都是可寫的。每個緩衝類都實現了isReadOnly()方法來表示緩衝內容是否允許被修改。

Channels

    緩衝需要配合Channel來使用Channel是I/O傳輸的入口,緩衝則是資料傳輸的源頭或目的地。在寫入時,你想傳送的資料首先被放入緩衝,接著被傳遞至一個Channel;在讀取時,Channel負責接收資料,然後寄存至你提供的緩衝中。(比如在網路傳輸時的過程:使用者資料——>傳送端緩衝——>傳送端Channel——>網路傳輸——>接收端Channel——>接收端緩衝——>使用者資料,譯者注)

    Channel就像一個管道一樣,將資料在管道兩端的位元組緩衝之間進行高效率的傳輸。它就像是一個閘道器,通過它可以用最小的成本來訪問作業系統本地的I/O服務,而緩衝則是在兩端內部的端點,Channel使用它來發送和接收資料。

Channel能在非阻塞模式下執行。一個非阻塞模式下的Channel不會讓呼叫執行緒睡眠。請求的操作要麼立刻完成、要麼返回一個結果告知什麼都沒做

  只有面向資料流的Channel,比如socket,能夠在非阻塞模式下執行。

在java.nio中的Channel包括FileChannel、ServerSocketChannel以及SocketChannel;這些都是為檔案和socket管理所提供的特定的Channel。

FileChannel

 FileChannel是可讀可寫的Channel,它必須阻塞,不能用在非阻塞模式中。面向資料流I/O的非阻塞風格並不適合面向檔案的操作,因為檔案I/O有本質上的區別。

FileChannel物件不能被直接建立。一個FileChannel例項只能通過在開啟的檔案物件(RandomAccessFile、FileInputStream、或FileOutputStream)上呼叫getChannel()得到。GetChannel()方法返回一個連線到相同檔案的FileChannel物件,與檔案物件擁有相同的訪問許可權。FileChannel物件是執行緒安全的。多執行緒能夠併發的呼叫同一個例項上的方法而不會導致任何問題,但是並非所有操作是支援多執行緒的。影響Channel位置或者檔案大小的操作必須是單執行緒的。

使用FileChannel,可以讓拷貝等操作變成Channel到Channel的傳輸(transferTo()和transferFrom()方法),而且讀寫操作更易於使用緩衝。

SocketChannel

SocketChannel與FileChannel不同:新的Socket Channel能在非阻塞模式下執行並且是可選擇的。不再需要為每個socket連線指派執行緒了。使用新的NIO類,一個或多個執行緒能管理成百上千個活動的socket連線,只用到很小甚至0的效能損失。使用Selector物件可以選擇可用的Socket Channel。

有3個Socket Channel型別:SocketChannel, ServerSocketChannel, 以及 DatagramChannel; SocketChannel和DatagramChannel是可讀可寫的,ServerSocketChannel 監聽到來的連線,並且建立新的SocketChannel 物件。所有這些SocketChannel初始化時會建立一個同等的socket物件(java.net sockets)。這個同等的socket能從Channel物件上呼叫socket()方法獲取。每個Socket Channel (in java.nio.channels)物件擁有一個相關的java.net.socket物件,反之並非所有的socket物件擁有相關的Channel物件。如果你使用傳統方式建立一個socket物件,直接初始化它,它將不會擁有一個相關的Channel物件,它的getChannel()方法會返回null。

Socket Channel能在非阻塞模式下執行。傳統Java Socket阻塞的本性曾經是影響Java應用可伸縮性的罪魁禍首之一。而基於非阻塞I/O則構建了很多複雜巧妙的、高效能的應用。設定或重置Channel的阻塞模式很簡單,只要呼叫configureBlocking()即可。

非阻塞socket常在伺服器端使用因為它能讓同時管理多個socket變得簡單。

Selector 選擇器

選擇器(Selector)提供了挑選可用狀態Channel的能力,從而實現多路複用的I/O。我下面的例子將很好的解釋選擇器的優勢:

設想你在一個火車站(non-selector,非選擇器場景),有3個平臺 (channels), 每個平臺都有火車到達(buffer,緩衝)。每個平臺上都有個管理員管理著到達的列車(worker thread,工作執行緒)。這是非選擇器的場景。現在設想下選擇器場景。有3個平臺(channels),每個平臺會有火車到達(緩衝),每個平臺都有個指示器(比如一個響鈴)指示著“火車到達”(selection key)。在這個場景下只有一個管理員就可以管理所有的3個平臺。他只需檢視指示器(selector.select())來判斷是否有火車到達然後去處理一下到達的列車。

理解selector場景的優勢很簡單:一個單執行緒就可以實現多重任務處理的應用。除此之外,使用非阻塞選擇器還能得到更多好處!設想火車管理員看著指示器時:他可以等待新的列車到達而不做其他事(使用selector.select()的阻塞模式);也可以在等待新的列車到達時(非阻塞模式使用selector.selectNow())去賣車票,這樣selector就會返回null讓執行緒執行其他程式碼邏輯。

IO vs. NIO

NIO使得I/O比傳統I/O更加高效。在一個I/O操作佔很高比例的程式中,想想看會有什麼不同。比如,如果一個應用需要使用socket拷貝檔案或者傳輸位元組,使用NIO可能會得到更快的效能,因為它比I/O的API更接近作業系統。隨著資料量的增大,效能提升將更可觀。除了io API裡,NIO還為資料流操作提供了其他的功能特性。但是,不可能用NIO取代IO,因為NIO的API是基於java.io之上擴充套件的功能。NIO通過擴充套件本地的IO API,為廣大開發者使用更加強大的方式操作資料流帶來了新的可能性。

普通 IO 類         Stream 流傳輸                                 IO = 流傳輸(單位元組傳輸)

NIO 類               NIO 塊傳輸 ( 輸入通道、緩衝區、輸出通道 )             NIO = 緩衝區 + 通道(塊傳輸)

緩衝區 

緩衝區 Buffer 是一個物件, 它包含一些要寫入或者剛讀出的資料。在 NIO 庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的。在寫入資料時,它是寫入到緩衝區中的。任何時候訪問 NIO中的資料,您都是將它放到緩衝區中。  緩衝區實質上是一個數組。通常它是一個位元組陣列,但是也可以使用其他種類的陣列。但是一個緩衝區不僅僅 是一個數組。緩衝區提供了對資料的結構化訪問,而且還可以跟蹤系統的讀/ 寫程序。

 緩衝區的型別

           最常用的緩衝區型別是 ByteBuffer 。一個 ByteBuffer 可以在其底層位元組陣列上進行 get/set 操作 ( 即位元組的獲取和設定 ) 。實際上針對 java 的每一種基本型別都有對應的一個緩衝區型別。

·         ByteBuffer

·         CharBuffer

·         ShortBuffer

·         IntBuffer

·         LongBuffer

·         FloatBuffer

·         DoubleBuffer

每一  Buffer 類都是 Buffer 介面的一個例項。 除了 ByteBuffer ,每一個 Buffer 類都有完全一樣的操作,只是它們所處理的資料型別不一樣。因為大多數標準 I/O 操作都使  ByteBuffer ,所以它具有所有共享的緩衝區操作以及一些特有的操作。

通道

     通道 Channel 是一個物件,可以通過它讀取和寫入資料。拿 NIO 與原來的 I/O 做個比較,通道就像是流。正如前面提到的,所有資料都通過 Buffer 物件來處理。您永遠不會將位元組直接寫入通道中,相反,您是將資料寫入包含一個或者多個位元組的緩衝區。同樣,您不會直接從通道中讀取位元組,而是將資料從通道讀入緩衝區,再從緩衝區獲取這個位元組。

通道型別

通道與流的不同之處在於通道是雙向的。而流只是在一個方向上移動 ( 一個流必須是 InputStream 或者OutputStream 的子類 ) , 而 通道 可以用於讀、寫或者同時用於讀寫。因為它們是雙向的,所以通道可以比流更好地反映底層作業系統的真實情況。特別是在 UNIX 模型中,底層作業系統通道是雙向的。

從檔案中讀取

如果使用原來的 I/O ,那麼我們只需建立一個 FileInputStream 並從它那裡讀取。而在 NIO 中,情況稍有不同:我們首先從 FileInputStream 獲取一個 FileChannel 物件,然後使用這個通道來讀取資料。

在 NIO 系統中,任何時候執行一個讀操作,您都是從通道中讀取,但是您不是直接 從通道讀取。因為所有資料最終都駐留在緩衝區中,所以您是從通道讀到緩衝區中。

因此讀取檔案涉及三個步驟: (1)  FileInputStream 獲取 Channel  (2) 建立 Buffer  (3) 將資料從Channel 讀到 Buffer 中。

第一步是從 FileInputStream 獲取通道:

FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();

下一步是建立緩衝區:

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

最後,將資料從通道讀到緩衝區中:

fc.read( buffer );

您會注意到,我們不需要告訴通道要讀多少資料 到緩衝區中。每一個緩衝區都有複雜的內部統計機制,它會跟蹤已經讀了多少資料以及還有多少空間可以容納更多的資料。

寫入檔案

在 NIO 中寫入檔案類似於從檔案中讀取。

首先從 FileOutputStream 獲取一個通道:

FileOutputStream fout = new FileOutputStream( "writesomebytes.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 );

注意在這裡同樣不需要告訴通道要寫入多資料。緩衝區的內部統計機制會跟蹤它包含多少資料以及還有多少資料要寫入。

在從輸入通道讀入緩衝區之前,我們呼叫 clear() 方法。同樣,在將緩衝區寫入輸出通道之前,我們呼叫 flip() 方法。 

 clear() 方法重設緩衝區,使它可以接受讀入的資料。

         flip() 方法讓緩衝區可以將新讀入的資料寫入另一個通道。


參考:http://www.ibm.com/developerworks/cn/java/j-lo-iocp/

        http://www.importnew.com/1178.html
        http://blog.csdn.net/zhaozheng7758/article/details/5255400