當學習了Java NIO和IO的API後,一個問題很快進入心中:我應該在什麼時候使用IO,什麼時候使用NIO呢?在本文中,我會盡可能清晰地闡明Java NIO和IO的差別、它們的用例,以及它們如何影響您的代碼設計。 緩存
下表總結了Java NIO和IO之間的主要差異,我會更詳細地描述表中每部分的差別。 服務器
IO NIO 網絡
面向流 面向緩衝 socket
阻塞IO 非阻塞IO 工具
選擇器 學習
Java NIO和IO之間第一個最大的區別是,IO是面向流的,其中NIO是面向緩衝區的。Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。此外,不能先後移動流中的數據。若是須要先後移動從流中讀取的數據,須要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不一樣。數據讀取到一個它之後處理的緩衝區,須要時,可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。可是,還須要檢查,是否該緩衝區包含全部您須要充分處理的數據。並且,需確保當更多的數據讀入緩衝區時,不要覆蓋還沒有處理的緩衝區數據。 spa
Java IO的各類流是阻塞的。這意味着,當一線程調用讀read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情。 Java NIO的非阻塞模式,使一個線程從某通道請求讀取數據,僅得目前可用的,或若目前沒有數據可用時,什麼都沒有。而不是保持線程阻塞,直至數據變得可供讀取,該線程能夠繼續其餘的事情。 非阻塞寫也是如此。一線程請求寫入一些數據到某通道,但不等待它徹底寫入,而後該線程同時能夠去作別的事情。 線程將不是阻塞的IO空閒時間調用一般花費在其它通道在此期間的IO執行上,亦即,一個單獨的線程如今能夠管理多個輸入和輸出通道。 線程
Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,能夠註冊一個選擇器的多個通道,而後使用一個單獨的線程「選擇」通道:已有輸入可用於處理,或某通道已準備寫入。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。 設計
不管您選擇IO或NIO工具箱,可能會影響您應用程序設計的如下幾個方面: 1) 對NIO或IO類的API調用。 2) 數據處理。 3) 用來處理數據的線程數。 code
固然,使用NIO的API調用時看起來與使用IO時有所不一樣,這也難怪,不是僅僅從一個InputStream逐字節讀取,數據必須先讀入緩衝區,而後從那裏處理。
使用純粹的NIO設計相較IO設計,數據處理也受到影響。
在IO設計中,咱們從InputStream或 Reader逐字節讀取數據。想象一下,正在處理一基於文本數據的行流,例如:
Name: Anna Age: 25 Email: anna@mailserver.com Phone: 1234567890該文本行的流能夠這樣處理:
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);
注意第二行,從通道讀取字節到ByteBuffer。當這個方法調用返回時,你不知道你所需的全部數據是否在緩衝區內。你所知道的是,該緩衝區包含一些字節,這使得處理有點困難。
想象一下,若是第一次 read(buffer)調用後,全部讀入緩衝區爲半行,例如,「Name:An」,你能處理數據嗎?顯然不能,須要等待,直到整行數據讀入緩存,在此以前,對數據的任何處理毫無心義。
因此,你怎麼知道是否該緩衝區包含足夠的數據能夠處理呢?好了,你不知道。發現的方法只能查看緩衝區中的數據。其結果是,在知道數據是否在其中前,須要檢查緩衝區幾回。這不只效率低下,並且可使方案設計雜亂不堪。例如:
ByteBuffer buffer = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buffer); while(! bufferFull(bytesRead) ) { bytesRead = inChannel.read(buffer); }
bufferFull()方法必須跟蹤有多少數據讀入緩衝區,並返回真或假,取決於緩衝區是否已滿。換句話說,若是緩衝區準備好被處理,它被認爲是滿了。
bufferFull()方法掃描緩衝區,但必須保持在bufferFull()方法被調用以前狀態相同。若是沒有,下一個讀入緩衝區的數據可能沒法讀到正確的位置。這是不可能的,但倒是須要注意的又一問題。
若是緩衝區已滿,它能夠被處理。若是它不滿,在特定的狀況下,也許能夠部分過處理其中存在的任何數據。許多狀況下並不是如此。
下圖展現了「緩衝區數據循環就緒」:
NIO可以讓您管理只使用一個(或幾個)單線程管理多個通道(網絡鏈接或文件),但付出的代價是,解析數據可能會比從一個阻塞流中讀取數據時較爲複雜。
若是須要管理成千上萬打開的鏈接共存,每次只能發送小部分數據,例如聊天服務器,實現NIO的服務器多是一個優點。一樣,若是你須要保持許多打開的鏈接到其餘計算機上,如P2P網絡中,使用一個單獨的線程來管理你全部出站鏈接,多是一個優點。一個線程多個鏈接的設計方案以下圖所示:
若是你有很是高的帶寬更少的鏈接,一次發送大量的數據,也許典型的IO服務器實現可能的最佳契合。下圖說明了一個典型的IO服務器設計: