1、背景html
你們都知道Java BIO,其全稱是java blocking IO,相對的Java NIO 全稱爲java non-blocking IO。顧名思義,java nio 是一種非阻塞IO。NIO是爲了彌補IO操做的不足而誕生的,NIO的一些新特性有:非阻塞I/O,選擇器,緩衝以及管道。管道(Channel),緩衝(Buffer) ,選擇器( Selector)是其主要特徵。提供基於緩衝區(buffer)的塊寫入/讀取,而之前的I/O是基於流(Stream)的方式,NIO基於塊的IO操做,將最耗時的緩存區讀取和填充交由底層操做系統實現,所以速度上要快得多。java
2、NIO的名詞解釋數組
一、Buffer緩存
Buffer是一個對象,它用來存放即將發送的數據和即將到來的數據。發送給一個通道的全部對象都必須首先放到緩衝區中;一樣地,從通道中讀取的任何數據都要讀到緩衝區中。Buffer是NIO核心思想,它與普通流IO的區別是,普通流IO直接把數據寫入或讀取到Stream對象中,而NIO是先把讀寫數據交給Buffer,後在用流處理的。Buffer實際上就是一個數組,一般是字節數組,可是這個數組提供了訪問數據的讀寫等操做屬性,如位置,容量,上限等概念。服務器
在NIO中,Buffer是一個頂層父類,它是一個抽象類,經常使用的Buffer的子類有:ByteBuffer、IntBuffer、CharBuffer、LongBuffer、DoubleBuffer、FloatBuffer、ShortBuffer。若是是對於文件讀寫,上面幾種Buffer均可能會用到。可是對於網絡讀寫來講,用的最多的是ByteBuffer。 網絡
前面提到,buffer其實是一個封裝的字節數組,有兩個重要組件:狀態變量和訪問方法。而buffer提供的訪問數據的讀寫等操做屬性,如位置,容量,上限等概念是依靠狀態變量實現的。能夠用三個值指定緩衝區在任意時刻的狀態:多線程
position
老是小於或者等於 limit
。向Buffer中寫數據有兩種方式:併發
(1) 從Channel寫到Buffer。如:int bytesRead = inChannel.read(buf); //read into buffer.dom
(2) 經過Buffer的put()方法寫到Buffer裏。如:buf.put(127); 函數
從Buffer中讀取數據也有兩種方式:
(1)從Buffer讀取數據到Channel。如: int bytesWritten = inChannel.write(buf);
(2)使用get()方法從Buffer中讀取數據。如:byte aByte = buf.get();
二、Channel (通道)
與Stream(流)的不一樣之處在於通道是雙向的,流只能在一個方向上操做(一個流必須是InputStream或者OutputStream的子類),而通道能夠用於讀,寫或者兩者同時進行,最關鍵的是能夠和多路複用器結合起來,提供狀態位,多路複用器可識別Channel所處的狀態。
通道能夠分兩大類:用於網絡讀寫的SelectableChannel,和用於文件操做的FileChannel。具體來講:經過FileChannel能夠從文件讀或者向文件寫入數據;經過SocketChannel,以TCP來向網絡鏈接的兩端讀寫數據;經過ServerSocketChanel可以監聽客戶端發起的TCP鏈接,併爲每一個TCP鏈接建立一個新的SocketChannel來進行數據讀寫;經過DatagramChannel,以UDP協議來向網絡鏈接的兩端讀寫數據。
三、Selector
Selector提供選擇已經就緒的任務的能力。簡單說,就是Selector會不斷輪詢註冊在Selector上的通道(Channel),若是這個通道發生了讀寫操做,這個通道就會處於就緒狀態,會被Selector察覺到,而後經過SelectionKey能夠取出就緒的Channel集合,從而進行IO操做。
一個Selector能夠負責成千上萬的通道,沒有上限。這也是JDK使用了epoll代替傳統的Select實現,得到鏈接句柄沒有限制。意味着咱們只須要一個線程負責Selector的輪詢,就能夠接入成百上千的客戶端,這是JDK NIO庫的巨大進步。
Selector類是NIO的核心類,Selector可以檢測多個註冊的通道上是否有事件發生,若是有事件發生,便獲取事件而後針對每一個事件進行相應的響應處理。這樣一來,只是用一個單線程就能夠管理多個通道,也就是管理多個鏈接。這樣使得只有在鏈接真正有讀寫事件發生時,纔會調用函數來進行讀寫,就大大地減小了系統開銷,而且沒必要爲每一個鏈接都建立一個線程,不用去維護多個線程,而且避免了多線程之間的上下文切換致使的開銷。
與Selector有關的一個關鍵類是SelectionKey,一個SelectionKey表示一個到達的事件,這2個類構成了服務端處理業務的關鍵邏輯。
3、標準的NIO步驟
一個簡單(標準)的NIO輸入輸出通常包含以下步驟:
1. 從數據源獲取通道 ;
2. 分配緩衝區 ;
3. 切換緩存區爲寫模式;
4. 從通道讀取數據寫入緩衝區;
5. 切換緩衝區爲讀模式 ;
6. 緩衝區數據寫入通道中 ;
7. 關閉資源。
package com.denny.aio.test; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class Test { public static void main(String[] args) throws IOException { RandomAccessFile formFile = new RandomAccessFile("src\\a.txt", "rw"); RandomAccessFile toFile = new RandomAccessFile("src\\b.txt", "rw"); //獲取channel FileChannel fromChannel = formFile.getChannel(); FileChannel toChannel = toFile.getChannel(); // 定義緩衝大小 int bufSize = 1024*4; // 定義緩衝 ByteBuffer byteBuffer = ByteBuffer.allocate(bufSize); int len = 0; // 將數據從源channel寫入到緩衝區 while( (len=fromChannel.read(byteBuffer)) !=-1 ){ //切換到讀模式 byteBuffer.flip(); //讀取緩衝區數據寫到目標channel toChannel.write(byteBuffer); // 清空緩衝 byteBuffer.clear(); } // 釋放資源 toChannel.close(); fromChannel.close(); } }
注意:
將數據寫到輸出通道中。在這以前,咱們必須調用 flip() 方法。這個方法作兩件很是重要的事:
(1) 它將 limit 設置爲當前 position。這意味着它包括之前讀到的全部字節,而且一個字節也很少。
(2)它將 position 設置爲 0。這意味着咱們獲得的下一個字節是第一個字節。
最後一步是調用緩衝區的 clear() 方法。這個方法重設緩衝區以便接收更多的字節。 Clear 作兩種很是重要的事情:
(1)它將 limit 設置爲與 capacity 相同。
(2)它設置 position 爲 0。
4、NIO和傳統的IO區別
1,IO是面向流的,NIO是面向塊(緩衝區)的。
IO面向流的操做一次一個字節地處理數據。一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據。,致使了數據的讀取和寫入效率不佳;
NIO面向塊的操做在一步中產生或者消費一個數據塊。按塊處理數據比按(流式的)字節處理數據要快得多,同時數據讀取到一個它稍後處理的緩衝區,須要時可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。通俗來講,NIO採起了「預讀」的方式,當你讀取某一部分數據時,他就會猜想你下一步可能會讀取的數據而預先緩衝下來。
2,IO是阻塞的,NIO是非阻塞的。
對於傳統的IO,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情了。
而對於NIO,使用一個線程發送讀取數據請求,沒有獲得響應以前,線程是空閒的,此時線程能夠去執行別的任務,而不是像IO中那樣只能等待響應完成。而在NIO的非阻塞模式下,線程發送數據與接收數據都是經過「通道」進行的,線程只須要去詢問通道是否有數據須要處理,有則處理,無則當即返回不會進行等待。線程一般將非阻塞IO的空閒時間用於處理其餘通道上的IO事件,使用一個單獨的線程就能夠管理多個輸入和輸出通道。那麼NIO是怎麼實現非阻塞的呢?其實原理很簡單,NIO是面向塊的,先把數據搬運過來,存放到一個緩衝區中,線程過一段時間來緩衝區看看,有沒有數據,這個樣線程就不須要始終關注IO了。
3,NIO和IO適用場景
NIO是爲彌補傳統IO的不足而誕生的,可是尺有所短寸有所長,NIO也有缺點,由於NIO是面向緩衝區的操做,每一次的數據處理都是對緩衝區進行的,那麼就會有一個問題,在數據處理以前必需要判斷緩衝區的數據是否完整或者已經讀取完畢,若是沒有,假設數據只讀取了一部分,那麼對不完整的數據處理沒有任何意義。因此每次數據處理以前都要檢測緩衝區數據。
那麼NIO和IO各適用的場景是什麼呢?
若是須要管理同時打開的成千上萬個鏈接,這些鏈接每次只是發送少許的數據,例如聊天服務器,這時候用NIO處理數據多是個很好的選擇。
而若是隻有少許的鏈接,而這些鏈接每次要發送大量的數據,這時候傳統的IO更合適。使用哪一種處理數據,須要在數據的響應等待時間和檢查緩衝區數據的時間上做比較來權衡選擇。
對於NIO和傳統IO,有一個網友講的生動的例子:之前的流老是堵塞的,一個線程只要對它進行操做,其它操做就會被堵塞,也就至關於水管沒有閥門,你伸手接水的時候,無論水到了沒有,你就都只能耗在接水(流)上。nio的Channel的加入,至關於增長了水龍頭(有閥門),雖然一個時刻也只能接一個水管的水,但依賴輪換策略,在水量不大的時候,各個水管裏流出來的水,均可以獲得妥善接納,這個關鍵之處就是增長了一個接水工,也就是Selector,他負責協調,也就是看哪根水管有水了的話,在當前水管的水接到必定程度的時候,就切換一下:臨時關上當前水龍頭,試着打開另外一個水龍頭(看看有沒有水)。當其餘人須要用水的時候,不是直接去接水,而是事前提了一個水桶給接水工,這個水桶就是Buffer。也就是,其餘人雖然也可能要等,但不會在現場等,而是回家等,能夠作其它事去,水接滿了,接水工會通知他們。
這其實也是很是接近當前社會分工細化的現實,也是統分利用現有資源達到併發效果的一種很經濟的手段,而不是動不動就來個並行處理,雖然那樣是最簡單的,但也是最浪費資源的方式。