前面的一些文章中我總結了一些Java IO和NIO相關的主要知識點,也是管中窺豹,IO類庫已經功能很強大了,可是Java 爲何又要引入NIO,這是我一直不是很清楚的?前面也只是簡單說起了一下:由於性能,可是僅僅是由於性能嗎,除此以外是否還有別的緣由,或者說既然NIO性能好,那爲何如今咱們還在使用IO。本節咱們就來詳細對比一下二者的特性以及二者之間的不一致對咱們編碼所帶來的影響。緩存
一樣,本文會主要圍繞下面幾個方面來總結: 服務器
兩種IO的各自適用場景socket
總結工具
二者之間的不一樣主要體如今以下三個方面:性能
在接下來的部分,咱們逐個討論這三個不一樣。學習
Java NIO和IO之間第一個不一樣點是IO是面向流(Stream)的而NIO是面向緩衝區(Buffer)的。開發工具
Java IO是面向流的,這意味着是一次性從流中讀取一批數據,這些數據並不會緩存在任何地方,而且對於在流中的數據是不支持在數據中先後移動。若是須要在這些數據中移動(爲何要移動,能夠屢次讀取),則仍是須要將這部分數據先緩存在緩衝區中。編碼
而Java NIO採用的是面向緩衝區的方式,有些不一樣,數據會先讀取到緩衝區中以供稍後處理。在buffer中是能夠方便地前移和後移,這使得在處理數據時能夠有更大的靈活性。可是呢須要檢查buffer是否包含須要的全部數據以便可以將其完整地處理,而且須要確保在經過channel往buffer讀數據的時候不可以覆蓋還未處理的數據。
Java IO中使用的流是屬於阻塞式的,意味着當線程調用其read()或write()方法時線程會阻塞,直到完成了數據的讀寫,在讀寫的過程當中線程是什麼都作不了的。
Java NIO提供了一種非阻塞模式,使得線程向channel請求讀數據時,只會獲取已經就緒的數據,並不會阻塞以等待全部數據都準備好(IO就是這樣作),這樣在數據準備的階段線程就可以去處理別的事情。對於非阻塞式寫數據是同樣的。線程往channel中寫數據時,並不會阻塞以等待數據寫完,而是能夠處理別的事情,等到數據已經寫好了,線程再處理這部分事情。
當線程在進行IO調用而且不會進入阻塞的狀況下,這部分的空餘時間就能夠花在和其餘channel進行IO交互上。也就是說,這樣單個線程就可以管理多個channel的輸入和輸出了。
Java NIO中的Selector容許單個線程監控多個channel,能夠將多個channel註冊到一個Selector中,而後能夠"select"出已經準備好數據的channel,或者準備好寫入的channel。這個selector機制使得單個線程同時管理多個channel變得更容易。
選擇使用NIO仍是IO做爲開發工具包會在以下幾個方面影響應用設計:
採用NIO的API調用方式和IO是不同的,與直接從InputStream中讀取字節數據不一樣,在NIO中,數據必需要先被讀到buffer中,而後再從那裏進行後續的處理。
採用NIO的設計仍是IO的設計,數據的處理方式也是不同的。
在IO設計中是從InputStream或Reader中逐字節讀取數據。在下面例子中,咱們經過一個處理基於文本的簡單例子來講明兩種設計的區別:
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890
採用IO的方式,這些數據流會像下面這樣處理:
InputStream input = ... ; // get the InputStream from the client socket BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String nameLine = reader.readLine(); String ageLine = reader.readLine(); String emailLine = reader.readLine(); String phoneLine = reader.readLine();
注意在這裏處理狀態是經過程序執行了多少就可以肯定的。換句話說,當第一行reader.readLine()返回以後,能夠肯定已經讀了一整行。由於readLine()會阻塞直到整行數據讀完。並且咱們可以確切地知道所讀取的這第一行是包含名字的。相似,第二次調用readLine()返回以後咱們確切地知道所讀取的內容包含年齡。
能夠知道,上面的程序只有當有新的數據是可讀時纔會進行處理,在每一步都知道數據是什麼。一旦執行讀寫的線程已經讀取了一些數據以後,是不可以再返回到前面的數據(由於流的方式只能讀取一次,很好理解,像水同樣,流完了就流完了,除非你把它裝到容器裏面)。上面程序中所遵循的原則以下圖所示:
而NIO的實現則看起來有些不一樣,以下:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer);
注意第二行是從channel讀取數據到buffer中,當read()方法返回時咱們是不知道是否全部須要的數據有沒有所有讀到buffer中,咱們知道的只是buffer中可能包含一部分數據,這會使得整個過程的處理有點麻煩。
假設,在第一次調用read()以後,全部讀到buffer中的數據只有半行,好比,"Name:An"。這時能夠處理數據嗎,顯然是不能夠的(由於尚未讀完),須要等到至少一行數據被讀到buffer中。
那麼咱們又如何來知道buffer中包含足夠能夠處理的數據呢?惟一的辦法只有檢查buffer中的數據了。因此結果就是咱們須要經過屢次檢查buffer中的數據來判斷數據是否已經所有讀進buffer了。這樣就很低效,並且容易致使程序設計混亂。好比:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { bytesRead = inChannel.read(buffer); }
bufferFull()方法會跟蹤有多少數據被讀到buffer中了,而且返回true或者false,取決於buffer是否已滿。換言之,若是buffer中的數據已經可供處理,那就表明它已經滿了。
bufferFull()方法會掃描整個buffer,要保證掃描並不會影響整個buffer的狀態,否則可能致使後面要讀入buffer中的數據不能讀到正確地位置。這並不是不可能,因此對於設計者來講這是一個須要關注的地方。
若是buffer已滿,那其中的數據就可供處理。若是沒滿,那可能須要部分地處理那些數據(若是須要的話),只是在大部分場景下是不須要的。
下圖描述了這種 is-data-in-buffer-ready的循環:
NIO使得經過單個或少許線程來管理多個channel(網絡鏈接或者文件)成爲可能,可是代價是傳遞數據會比從阻塞的流中讀數據更復雜。咱們學習一項新的技術時,既要看到其優勢也要看到其缺點。
若是須要同時管理數以千計的鏈接,並且每一個鏈接只會發送少許的數據,好比聊天服務器,用NIO的方式來實現這個服務器則比較合適。相似的,若是須要長時間保持一些和別的電腦的鏈接,好比在一個P2P網絡中,用單個線程來管理全部的對外鏈接也有優點。以下圖描述了這種單個線程,多個鏈接的設計模型:
若是隻有少許的鏈接,可是每一個鏈接又都佔用大量的帶寬,短期以內發送大量數據,這時後也許傳統的IO模型會更適用,由於專注,因此在特定場景下能夠更高效。以下圖描述了一個基於傳統IO模型設計的服務器模型:
在前面總結了不少IO和NIO的相關知識以後,本文總結了Java中兩種IO類庫的區別即各自的優缺點:
技術沒有好壞,只有合適與否!