在研究Java NIO和IO API時,很快就會出現(xiàn)一個問題:
我什么時候應(yīng)該使用IO,什么時候應(yīng)該使用NIO?
在本文中晨缴,我將嘗試闡明Java NIO和IO之間的差異,它們的用例以及它們?nèi)绾斡绊懘a的設(shè)計峡捡。
Java NIO和IO的主要區(qū)別
下表總結(jié)了Java NIO和IO之間的主要區(qū)別击碗。我將在表格后面的部分中詳細(xì)介紹每個區(qū)別。
IO | NIO |
---|---|
流導(dǎo)向 | 緩沖導(dǎo)向 |
阻止IO | 非阻塞IO |
選擇器 |
面向流的與面向緩沖的
Java NIO和IO之間的第一個重要區(qū)別是IO是面向流的们拙,其中NIO是面向緩沖區(qū)的稍途。那么,這意味著什么砚婆?
面向流的Java IO意味著你可以從流中一次讀取一個或多個字節(jié)械拍。你對讀取的字節(jié)做什么取決于你。它們不會緩存在任何地方装盯。此外坷虑,你無法在流中的數(shù)據(jù)中前后移動。如果需要在從流中讀取的數(shù)據(jù)中前后移動埂奈,則需要先將其緩存在緩沖區(qū)中迄损。
Java NIO的面向緩沖區(qū)的方法略有不同。數(shù)據(jù)被讀入緩沖區(qū)账磺,稍后處理該緩沖區(qū)芹敌。你可以根據(jù)需要在緩沖區(qū)中前后移動。這使你在處理過程中具有更大的靈活性垮抗。但是氏捞,你還需要檢查緩沖區(qū)是否包含完整處理所需的所有數(shù)據(jù)。并且冒版,你需要確保在將更多數(shù)據(jù)讀入緩沖區(qū)時液茎,不要覆蓋尚未處理的緩沖區(qū)中的數(shù)據(jù)。
阻止與非阻塞IO
Java IO的各種流都是阻塞的。這意味著豁护,當(dāng)線程調(diào)用read()或write()時哼凯,該線程將被阻塞,直到有一些數(shù)據(jù)要讀取楚里,或者數(shù)據(jù)被完全寫入。在此期間猎贴,該線程無法執(zhí)行任何其他操作班缎。
Java NIO的非阻塞模式允許線程請求從通道讀取數(shù)據(jù),并且只獲取當(dāng)前可用的內(nèi)容她渴,或者根本沒有數(shù)據(jù)达址,如果當(dāng)前沒有數(shù)據(jù)可用。線程可以繼續(xù)使用其他內(nèi)容趁耗,而不是在數(shù)據(jù)可供讀取之前保持阻塞狀態(tài)沉唠。
非阻塞寫作也是如此。線程可以請求將某些數(shù)據(jù)寫入通道苛败,但不要等待它完全寫入满葛。然后線程可以繼續(xù)并在同一時間做其他事情。
什么線程在IO調(diào)用中沒有阻塞時花費(fèi)空閑時間罢屈,通常在此期間在其他通道上執(zhí)行IO嘀韧。也就是說,單個線程現(xiàn)在可以管理多個輸入和輸出通道缠捌。
選擇
Java NIO的選擇器允許單個線程監(jiān)視多個輸入通道锄贷。你可以使用選擇器注冊多個通道,然后使用單個線程“選擇”具有可用于處理的輸入的通道曼月,或者選擇準(zhǔn)備寫入的通道谊却。這種選擇器機(jī)制使單個線程可以輕松管理多個通道。
NIO和IO如何影響應(yīng)用程序設(shè)計
無論你選擇NIO還是IO哑芹,因?yàn)槟愕腎O工具包可能會影響應(yīng)用程序設(shè)計的以下方面:
- API調(diào)用NIO或IO類炎辨。
- 處理數(shù)據(jù)。
- 用于處理數(shù)據(jù)的線程數(shù)绩衷。
API調(diào)用
當(dāng)然蹦魔,使用NIO時的API調(diào)用看起來與使用IO時不同。這并不奇怪咳燕。而不是僅僅從例如InputStream讀取字節(jié)的數(shù)據(jù)字節(jié)勿决,必須首先將數(shù)據(jù)讀入緩沖區(qū),然后從那里進(jìn)行處理招盲。
數(shù)據(jù)處理
使用純NIO設(shè)計與IO設(shè)計時低缩,數(shù)據(jù)處理也會受到影響。
在IO設(shè)計中,你從InputStream或Reader中讀取字節(jié)的數(shù)據(jù)字節(jié)咆繁。想象一下讳推,你正在處理基于行的文本數(shù)據(jù)流。例如:
姓名:qq
年齡:7歲
電子郵件:qq@mail.com
電話:1234567890
這個文本行流可以像這樣處理:
InputStream input = ...; //從客戶端套接字獲取InputStream
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();
注意處理狀態(tài)是如何由程序執(zhí)行的程度決定的玩般。換句話說银觅,一旦第一個reader.readLine()方法返回,你就確定已經(jīng)讀取了整行文本坏为。readLine()會阻塞直到讀取整行究驴,這就是原因。你還知道此行包含名稱匀伏。同樣洒忧,當(dāng)?shù)诙€readLine()調(diào)用返回時,你知道此行包含年齡等够颠。
正如你所看到的熙侍,只有當(dāng)有新數(shù)據(jù)要讀取時,程序才會進(jìn)行履磨,并且對于每個步驟蛉抓,你都知道該數(shù)據(jù)是什么。一旦執(zhí)行的線程已經(jīng)超過讀取代碼中的某個數(shù)據(jù)片段蹬耘,該線程就不會在數(shù)據(jù)中向后移動(通常不會)芝雪。此圖中還說明了此原則:
|
NIO的實(shí)現(xiàn)看起來會有所不同。這是一個簡化的例子:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
注意第二行從通道讀取字節(jié)到ByteBuffer综苔。當(dāng)該方法調(diào)用返回時惩系,你不知道所需的所有數(shù)據(jù)是否都在緩沖區(qū)內(nèi)。你只知道緩沖區(qū)包含一些字節(jié)如筛。這使得處理更加困難堡牡。
想象一下,在第一次讀妊钆佟(緩沖)調(diào)用之后晤柄,是否所有讀入緩沖區(qū)的內(nèi)容都是半行。例如妖胀,“姓名:An”芥颈。你能處理這些數(shù)據(jù)嗎?并不是的赚抡。在完成任何數(shù)據(jù)的處理之前爬坑,你需要等待至少一整行數(shù)據(jù)進(jìn)入緩沖區(qū)。
那么你怎么知道緩沖區(qū)是否包含足夠的數(shù)據(jù)來處理它涂臣?好吧盾计,你沒有。找出的唯一方法是查看緩沖區(qū)中的數(shù)據(jù)。結(jié)果是署辉,在你知道所有數(shù)據(jù)是否存在之前族铆,你可能需要多次檢查緩沖區(qū)中的數(shù)據(jù)。這既低效又可能在程序設(shè)計方面變得混亂哭尝。例如:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(哥攘!bufferFull(bytesRead)){
bytesRead = inChannel.read(buffer);
}
bufferFull()方法必須跟蹤讀入緩沖區(qū)的數(shù)據(jù)量,并返回true或false刚夺,具體取決于緩沖區(qū)是否已滿献丑。換句話說,如果緩沖區(qū)已準(zhǔn)備好進(jìn)行處理侠姑,則認(rèn)為它已滿。
bufferFull()方法掃描緩沖區(qū)箩做,但必須使緩沖區(qū)保持與調(diào)用bufferFull()方法之前相同的狀態(tài)莽红。如果不是,則可能無法在正確的位置讀入讀入緩沖區(qū)的下一個數(shù)據(jù)邦邦。這不是不可能的安吁,但這是另一個需要注意的問題。
如果緩沖區(qū)已滿燃辖,則可以對其進(jìn)行處理鬼店。如果它不滿,你可能能夠部分處理那里的任何數(shù)據(jù)黔龟,如果這在你的特定情況下是有意義的妇智。在許多情況下,它沒有氏身。
這個圖中說明了is-data-in-buffer-ready循環(huán):
在這里插入圖片描述
|
---|
Java NIO:從通道讀取數(shù)據(jù)巍棱,直到所有需要的數(shù)據(jù)都在緩沖區(qū)中。 |
摘要
NIO允許你僅使用一個(或幾個)線程來管理多個通道(網(wǎng)絡(luò)連接或文件)蛋欣,但成本是解析數(shù)據(jù)可能比從阻塞流中讀取數(shù)據(jù)時更復(fù)雜航徙。
如果你需要同時管理數(shù)千個打開的連接,每個只發(fā)送一些數(shù)據(jù)陷虎,例如聊天服務(wù)器到踏,在NIO中實(shí)現(xiàn)服務(wù)器可能是一個優(yōu)勢。同樣尚猿,如果你需要與其他計算機(jī)保持大量開放連接窝稿,例如在P2P網(wǎng)絡(luò)中,使用單個線程來管理所有出站連接可能是一個優(yōu)勢谊路。此圖中說明了這一個線程讹躯,多個連接設(shè)計:
在這里插入圖片描述
|
---|
Java NIO:管理多個連接的單個線程。 |
如果你擁有較少帶寬的連接,一次發(fā)送大量數(shù)據(jù)潮梯,那么可能最經(jīng)典的IO服務(wù)器實(shí)現(xiàn)可能是最合適的骗灶。此圖說明了經(jīng)典的IO服務(wù)器設(shè)計:
在這里插入圖片描述
|
---|
Java IO:經(jīng)典的IO服務(wù)器設(shè)計 - 由一個線程處理的一個連接。 |