Java NIO與IO的區別與應用 [復制鏈接]

2019-6-19 11:14
EmailLi 閱讀:137 評論:0 贊:0
Tag:  JavaNIO

在研究Java NIO和IO API時,很快就會發現一個問題:

我什么時候應該使用IO,什么時候應該使用NIO?

在本文中,我將嘗試闡明Java NIO和IO之間的差異,它們的用例以及它們如何影響代碼的設計。

Java NIO和IO的主要區別

下表總結了Java NIO和IO之間的主要區別。我將在表格后面的部分中詳細介紹每個區別。

IONIOStream orientedBuffer orientedBlocking IONon blocking IO

Selectors

Stream Oriented vs. Buffer Oriented

Java NIO和IO之間的第一個重要區別是IO是面向流的,其中NIO是面向緩沖區的。那么,這意味著什么?

面向流的Java IO意味著您可以從流中一次讀取一個或多個字節。你對讀取的字節做什么取決于你。它們不會緩存在任何地方。此外,您無法在流中的數據中前后移動。如果需要在從流中讀取的數據中前后移動,則需要先將其緩存在緩沖區中。

Java NIO的面向緩沖區的方法略有不同。數據被讀入緩沖區,稍后處理該緩沖區。你可以根據需要在緩沖區中前后移動。這使你在處理過程中具有更大的靈活性。但是,你還需要檢查緩沖區是否包含完整處理所需的所有數據。并且,你需要確保在將更多數據讀入緩沖區時,不要覆蓋尚未處理的緩沖區中的數據。

Blocking vs. Non-blocking IO

Java IO的各種流都是blocking的。這意味著,當線程調用read()或write()時,該線程將被阻塞,直到有一些數據要讀取,或者數據被完全寫入,在此期間,該線程無法執行任何其他操作。

Java NIO的非阻塞模式允許線程請求從通道讀取數據,并且只獲取當前可用的內容,或者根本沒有數據,如果當前沒有數據可用。線程可以繼續使用其他內容,而不是在數據可供讀取之前保持阻塞狀態。

非阻塞寫入也是如此,線程可以請求將某些數據寫入通道,但不要等待它完全寫入。然后線程可以繼續并在同一時間做其他事情。

線程在IO調用中沒有阻塞時花費空閑時間,通常在此期間在其他通道上執行IO。也就是說,單個線程現在可以管理多個輸入和輸出通道。

Selectors

Java NIO的選擇器允許單個線程監視多個輸入通道。你可以使用選擇器注冊多個通道,然后使用單個線程“選擇”具有可用于處理的輸入的通道,或者選擇準備寫入的通道。這種選擇器機制使單個線程可以輕松管理多個通道。


NIO和IO如何影響應用程序設計

選擇NIO或IO作為IO工具包可能會影響應用程序設計的以下方面:

  1. API調用NIO或IO類。
  2. 處理數據。
  3. 用于處理數據的線程數。

API調用

當然,使用NIO時的API調用看起來與使用IO時不同。這并不奇怪。而不是僅僅從例如InputStream讀取字節的數據字節,必須首先將數據讀入緩沖區,然后從那里進行處理。

數據處理


使用純NIO設計與IO設計時,數據處理也會受到影響。

在IO設計中,您從InputStream或Reader中讀取字節的數據字節。想象一下,您正在處理基于行的文本數據流。例如:

Name: Anna
Age: 25
Email: [email protected]
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()調用返回時,您知道此行包含年齡等。

正如您所看到的,只有當有新數據要讀取時,程序才會進行,并且對于每個步驟,您都知道該數據是什么。一旦執行的線程已經超過讀取代碼中的某個數據片段,該線程就不會在數據中向后移動(通常不會)。此圖中還說明了此原則:


Java IO:從阻塞流中讀取數據。

NIO的實現看起來會有所不同。這是一個簡化的例子:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);

注意第二行從通道讀取字節到ByteBuffer。當該方法調用返回時,您不知道所需的所有數據是否都在緩沖區內。你只知道緩沖區包含一些字節,這使得處理更加困難。

想象一下,在第一次讀取(緩沖)調用之后,是否所有讀入緩沖區的內容都是半行。例如,“姓名:An”。你能處理這些數據嗎?并不是的。在完成任何數據的處理之前,您需要等待至少一整行數據進入緩沖區。

那么你怎么知道緩沖區是否包含足夠的數據來處理它?好吧,你沒有。找出的唯一方法是查看緩沖區中的數據。結果是,在您知道所有數據是否存在之前,您可能需要多次檢查緩沖區中的數據。這既低效又可能在程序設計方面變得混亂。例如:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}

bufferFull()方法必須跟蹤讀入緩沖區的數據量,并返回true或false,具體取決于緩沖區是否已滿。換句話說,如果緩沖區已準備好進行處理,則認為它已滿。

bufferFull()方法掃描緩沖區,但必須使緩沖區保持與調用bufferFull()方法之前相同的狀態。如果不是,則可能無法在正確的位置讀入讀入緩沖區的下一個數據。這不是不可能的,但這是另一個需要注意的問題。

如果緩沖區已滿,則可以對其進行處理。如果它不滿,您可能能夠部分處理那里的任何數據,如果這在您的特定情況下是有意義的。在許多情況下,它沒有。

這個圖中說明了is-data-in-buffer-ready循環:


Java NIO:從通道讀取數據,直到所有需要的數據都在緩沖區中。

摘要

NIO允許您僅使用一個(或幾個)線程來管理多個通道(網絡連接或文件),但成本是解析數據可能比從阻塞流中讀取數據時更復雜。

如果您需要同時管理數千個打開的連接,每個只發送一些數據,例如聊天服務器,在NIO中實現服務器可能是一個優勢。同樣,如果您需要與其他計算機保持大量開放連接,例如在P2P網絡中,使用單個線程來管理所有出站連接可能是一個優勢。此圖中說明了這一個線程,多個連接設計:


Java NIO:管理多個連接的單個線程。

如果您擁有較少帶寬的連接,一次發送大量數據,那么可能最經典的IO服務器實現可能是最合適的。此圖說明了經典的IO服務器設計:


Java IO:經典的IO服務器設計 - 由一個線程處理的一個連接。

簡化理解
就讀取速度來說:CPU > 內存 > 硬盤
I- 就是從硬盤到內存
O- 就是從內存到硬盤
第一種方式:從硬盤讀取數據,然后程序一直等,數據讀完后,繼續你的操作。這種方式是最簡單的,叫阻塞IO。
第二種方式:從硬盤讀取數據,然后程序繼續向下執行,等數據讀取完后,通知當前程序讀取完成(對硬件來說叫中斷,對程序來說叫回調),然后此程序可以立即處理讀取的數據,也可以執行完當前操作后再對讀取完的數據進行操作。

總結


操作系統是按塊 Block從硬盤拿數據,就如同一個大臉盆,一下子就放入了一盆水。但是,當 Java 使用的時候,舊的 IO 確實基于 流 Stream的,也就是雖然操作系統給我了一臉盆水,但是我得用吸管慢慢喝。
于是,NIO 橫空出世。。
分享到:
我來說兩句
facelist
您需要登錄后才可以評論 登錄 | 立即注冊
所有評論(0)

領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

两码中特期期