Java:IO和NIO

一、幾種IO模型

1.阻塞IO模型(我就死等)

最傳統(tǒng)的一種IO模型,即在讀寫數(shù)據(jù)過程中會發(fā)生阻塞現(xiàn)象。

當用戶線程發(fā)出IO請求之后,內(nèi)核會去查看數(shù)據(jù)是否就緒,如果沒有就緒就會等待數(shù)據(jù)就緒,而用戶線程就
會處于阻塞狀態(tài),用 戶線程交出CPU。當數(shù)據(jù)就緒之后,內(nèi)核會將數(shù)據(jù)拷貝到用戶線程,并返回結果給用戶線程,用戶線程才解除block狀態(tài)。

典型的阻塞IO模型的例子如下,如果數(shù)據(jù)沒有就緒,就會一直阻塞在read方法。

data = socket.read();

2.非阻塞IO模型 (我就一直問)

當用戶線程發(fā)起一個read操作后,并不需要等待,而是馬上就得到了一個結果,結果可能是數(shù)據(jù),也可能是error。

如果結果是一個 error時,它就知道數(shù)據(jù)還沒有準備好,于是它可以再次發(fā)送read操作。一旦內(nèi)核中的數(shù)據(jù)準備 好了,并且又再次收到了用戶線程的請求,那么它馬上就將數(shù)據(jù)拷貝到了用戶線程,然后返回。

所以在非阻塞IO模型中,用戶線程需要不斷地詢問內(nèi)核數(shù)據(jù)是否就緒,也就說非阻塞IO不會交出CPU,而會一直占用CPU。這樣就會有一個嚴重的問題:在while循環(huán)中需要不斷地去詢問內(nèi)核數(shù)據(jù)是否就緒,這樣會導致CPU占用率非常高,因此一般情況下很少使用while循環(huán)這種方式來讀取數(shù)據(jù)。

典型的非阻塞IO模型一般如下:

while(true){  
    data = socket.read();  
    if (data != error) {  
        處理數(shù)據(jù)  
        break;  
    } 
} 

3. 多路復用IO模型 (小組長一直問)

多路復用IO模型是目前使用得比較多的模型。Java NIO實際上就是多路復用IO。在多路復用IO模型中,會有一個線程不斷去輪詢多個socket的狀態(tài),只有當socket真正有讀寫事件時,才真 正調(diào)用實際的IO讀寫操作

因為在多路復用IO模型中,只需要使用一個線程就可以管理多個 socket,系統(tǒng)不需要建立新的進程或者線程,也不必維護這些線程和進程,并且只有在真正有 socket讀寫事件進行時,才會使用IO資源,所以它大大減少了資源占用。

在Java NIO中,是通 過selector.select()去查詢每個通道是否有到達事件,如果沒有事件,則一直阻塞在那里,因此這種方式會導致用戶線程的阻塞。多路復用IO模式,通過一個線程就可以管理多個socket,只有當 socket真正有讀寫事件發(fā)生才會占用資源來進行實際的讀寫操作。因此,多路復用IO比較適合連接數(shù)比較多的情況。

多路復用IO比非阻塞IO模型的效率高,因為在非阻塞IO中不斷地詢問socket狀態(tài)是通過用戶線程去進行的,而在多路復用IO 中,輪詢每個socket狀態(tài)是內(nèi)核在進行的,這個效率要比用戶線程要高的多。

路復用IO模型是通過輪詢的方式來檢測是否有事件到達,并且對到達的事件 逐一進行響應。因此對于多路復用IO模型來說,一旦事件響應體很大,那么就會導致后續(xù)的事件 遲遲得不到處理,并且會影響新的事件輪詢。

4.信號驅(qū)動IO模型(弄好了叫我)

在信號驅(qū)動IO 模型中,當用戶線程發(fā)起一個IO請求操作,會給對應的socket注冊一個信號函數(shù),然后用戶線程會繼續(xù)執(zhí)行,當內(nèi)核數(shù)據(jù)就緒時會發(fā)送一個信號給用戶線程,用戶線程接收到 信號之后,便在信號函數(shù)中調(diào)用IO 讀寫操作來進行實際的IO 請求操作。

5. 異步IO模型(活就交給你了)

最理想的IO 模型。在異步IO 模型中,當用戶線程發(fā)起read操作之后,立刻就可以開始去做其它的事。而另一方面,從內(nèi)核的角度,當它受到一個asynchronous read之后, 它會立刻返回,說明read請求已經(jīng)成功發(fā)起了,因此不會對用戶線程產(chǎn)生任何block。然后,內(nèi)核會等待數(shù)據(jù)準備完成,然后將數(shù)據(jù)拷貝到用戶線程,當這一切都完成之后,內(nèi)核會給用戶線程 發(fā)送一個信號,告訴它read操作完成了。也就說用戶線程完全不需要實際的整個IO操作是如何 進行的,只需要先發(fā)起一個請求,當接收內(nèi)核返回的成功信號時表示IO操作已經(jīng)完成,可以直接 去使用數(shù)據(jù)了。

在異步IO模型中,IO操作的兩個階段都不會阻塞用戶線程,這兩個階段都是由內(nèi)核自動完 成,然后發(fā)送一個信號告知用戶線程操作已完成。用戶線程中不需要再次調(diào)用IO 函數(shù)進行具體的 讀寫。
在信號驅(qū)動模型中,當用戶線程接收到信號表示數(shù)據(jù) 已經(jīng)就緒,然后需要用戶線程調(diào)用IO函數(shù)進行實際的讀寫操作;而在異步IO模型中,收到信號表示IO操作已經(jīng)完成,不需要再在用戶線程中調(diào)用IO函數(shù)進行實際的讀寫操作。

二、Java IO模型

三、Java NIO模型

四、 IO和NIO

面向流和面向緩沖區(qū)

IO面向流,這意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,由于沒有緩存區(qū),它不能前后移動流中的數(shù)據(jù)。

NIO面向緩沖區(qū),數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動。這就增加了處理過程中的靈活性。
但是,還需要檢查是否該緩沖區(qū)中包含所有需要處理的數(shù)據(jù)。并且需確保當更多的數(shù)據(jù)讀入緩沖區(qū)時,不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。

阻塞和非阻塞

IO的各種流是阻塞的。這意味著,當一個線程調(diào)用read() 或 write()時,該線程被阻塞,直到有 一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。

NIO的非阻塞模式, 使一個線程從某通道發(fā)送請求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執(zhí)行IO操作,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道(channel)。

Java IO

字節(jié)流(單字節(jié)運輸,給計算機看):InputStream和OutputStream
字符流(多字節(jié)運輸,給人看):Reader和Writer

Java NIO

NIO主要有三大核心部分:Channel(通道),Buffer(緩沖區(qū)), Selector。
傳統(tǒng)IO基于字節(jié)流和字符流進行操作,而NIO基于Channel和Buffer(緩沖區(qū))進行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(選擇區(qū))用于監(jiān)聽多個通道的事件(比如:連接打開, 數(shù)據(jù)到達)。因此,單個線程可以監(jiān)聽多個數(shù)據(jù)通道。

Channel:和 IO 中的 Stream(流) 是差不多一個 等級的。只不過Stream是單向的,譬如:InputStream, OutputStream,而Channel是雙向的,既可以用來進行讀操作,又可以用來進行寫操作。

NIO中的Channel的主要實現(xiàn)有:

  1. FileChannel 2. DatagramChannel 3. SocketChannel 4. ServerSocketChannel
    分別可以對應文件IO、UDP和TCP(Server和Client)。

Buffer: 緩沖區(qū),實際上是一個容器,是一個連續(xù)數(shù)組。Channel提供從文件、 網(wǎng)絡讀取數(shù)據(jù)的渠道,但是讀取或?qū)懭氲臄?shù)據(jù)都必須經(jīng)由Buffer。

上圖描述了從一個客戶端向服務端發(fā)送數(shù)據(jù),然后服務端接收數(shù)據(jù)的過程。
客戶端發(fā)送數(shù)據(jù)時,必須先將數(shù)據(jù)存入Buffer中,然后將Buffer中的內(nèi)容寫入通道。
服務端這邊接收數(shù)據(jù)必須通過Channel將數(shù)據(jù)讀入到Buffer中,然后再從Buffer中取出數(shù)據(jù)來處理。

在NIO中,Buffer是一個頂層父類,它是一個抽象類,常用的Buffer的子類有: ByteBuffer、IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、FloatBuffer、 ShortBuffer

Selector: NIO的核心類,檢測多個注冊的通道上是否有事件發(fā)生,如果有事 件發(fā)生,便獲取事件然后針對每個事件進行相應的響應處理。這樣一來,只是用一個單線程就可以管理多個通道,也就是管理多個連接。這樣使得只有在連接真正有讀寫事件發(fā)生時,才會調(diào)用函數(shù)來進行讀寫,就大大地減少了系統(tǒng)開銷,并且不必為每個連接都創(chuàng)建一個線程,不用去維護多個線程,并且避免了多線程之間的上下文切換導致的開銷。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容