IO與NIO

1、阻塞與非阻塞

阻塞與非阻塞是描述進程在訪問某個資源時,數(shù)據(jù)是否準備就緒的的一種處理方式。當(dāng)數(shù)據(jù)沒有準備就緒時:

  • 阻塞:線程持續(xù)等待資源中數(shù)據(jù)準備完成,直到返回響應(yīng)結(jié)果。
  • 非阻塞:線程直接返回結(jié)果,不會持續(xù)等待資源準備數(shù)據(jù)結(jié)束后才響應(yīng)結(jié)果。

2、同步與異步

  • 同步與異步是指訪問數(shù)據(jù)的機制,同步一般指主動請求并等待IO操作完成的方式。
  • 異步則指主動請求數(shù)據(jù)后便可以繼續(xù)處理其它任務(wù),隨后等待IO操作完畢的通知。

老王燒開水:
1、普通水壺煮水,站在旁邊,主動的看水開了沒有?同步的阻塞
2、普通水壺煮水,去干點別的事,每過一段時間去看看水開了沒有,水沒開就走人。 同步非阻塞
3、響水壺煮水,站在旁邊,不會每過一段時間主動看水開了沒有。如果水開了,水壺自動通知他。 異步阻塞
4、響水壺煮水,去干點別的事,如果水開了,水壺自動通知他。異步非阻塞

基礎(chǔ)概念

1、傳統(tǒng)BIO模型

傳統(tǒng)BIO是一種同步的阻塞IO,IO在進行讀寫時,該線程將被阻塞,線程無法進行其它操作。
IO流在讀取時,會阻塞。直到發(fā)生以下情況:1、有數(shù)據(jù)可以讀取。2、數(shù)據(jù)讀取完成。3、發(fā)生異常

2、偽異步IO模型

以傳統(tǒng)BIO模型為基礎(chǔ),通過線程池的方式維護所有的IO線程,實現(xiàn)相對高效的線程開銷及管理。

3、NIO模型

NIO(JDK1.4)模型是一種同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(緩沖區(qū)), Selector(多路復(fù)用器)。傳統(tǒng)IO基于字節(jié)流和字符流進行操作,而NIO基于Channel和Buffer(緩沖區(qū))進行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(多路復(fù)用器)用于監(jiān)聽多個通道的事件(比如:連接打開,數(shù)據(jù)到達)。因此,單個線程可以監(jiān)聽多個數(shù)據(jù)通道。
NIO和傳統(tǒng)IO(一下簡稱IO)之間第一個最大的區(qū)別是,IO是面向流的,NIO是面向緩沖區(qū)的。 Java IO面向流意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動流中的數(shù)據(jù)。如果需要前后移動從流中讀取的數(shù)據(jù),需要先將它緩存到一個緩沖區(qū)。NIO的緩沖導(dǎo)向方法略有不同。數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時,不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。
IO的各種流是阻塞的。這意味著,當(dāng)一個線程調(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)。

NIO優(yōu)點:

  1. 通過Channel注冊到Selector上的狀態(tài)來實現(xiàn)一種客戶端與服務(wù)端的通信。
  2. Channel中數(shù)據(jù)的讀取是通過Buffer , 一種非阻塞的讀取方式。
  3. Selector 多路復(fù)用器 單線程模型, 線程的資源開銷相對比較小。

Channel(通道)

傳統(tǒng)IO操作對read()或write()方法的調(diào)用,可能會因為沒有數(shù)據(jù)可讀/可寫而阻塞,直到有數(shù)據(jù)響應(yīng)。也就是說讀寫數(shù)據(jù)的IO調(diào)用,可能會無限期的阻塞等待,效率依賴網(wǎng)絡(luò)傳輸?shù)乃俣?。最重要的是在調(diào)用一個方法前,無法知道是否會被阻塞。

NIO的Channel抽象了一個重要特征就是可以通過配置它的阻塞行為,來實現(xiàn)非阻塞式的通道。

Channel是一個雙向通道,與傳統(tǒng)IO操作只允許單向的讀寫不同的是,NIO的Channel允許在一個通道上進行讀和寫的操作。

FileChannel:文件

SocketChannel:

ServerSocketChannel:

DatagramChannel: UDP

Buffer(緩沖區(qū))

Bufer顧名思義,它是一個緩沖區(qū),實際上是一個容器,一個連續(xù)數(shù)組。Channel提供從文件、網(wǎng)絡(luò)讀取數(shù)據(jù)的渠道,但是讀寫的數(shù)據(jù)都必須經(jīng)過Buffer。


Buffer(緩沖區(qū))

Buffer緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝成NIO Buffer對象,并提供了一組方法,用來方便的訪問該模塊內(nèi)存。為了理解Buffer的工作原理,需要熟悉它的三個屬性:capacity、position和limit。

position和limit的含義取決于Buffer處在讀模式還是寫模式。不管Buffer處在什么模式,capacity的含義總是一樣的。見下圖:


capacity、position和limit
  • capacity:作為一個內(nèi)存塊,Buffer有固定的大小值,也叫作“capacity”,只能往其中寫入capacity個byte、long、char等類型。一旦Buffer滿了,需要將其清空(通過讀數(shù)據(jù)或者清楚數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)。
  • position:當(dāng)你寫數(shù)據(jù)到Buffer中時,position表示當(dāng)前的位置。出事的position值為0,當(dāng)寫入一個字節(jié)數(shù)據(jù)到Buffer中后,position會向前移動到下一個可插入數(shù)據(jù)的Buffer單元。position最大可為capacity-1。當(dāng)讀取數(shù)據(jù)時,也是從某個特定位置讀,講Buffer從寫模式切換到讀模式,position會被重置為0。當(dāng)從Buffer的position處讀取一個字節(jié)數(shù)據(jù)后,position向前移動到下一個可讀的位置。
  • limit:在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。 寫模式下,limit等于Buffer的capacity。當(dāng)切換Buffer到讀模式時, limit表示你最多能讀到多少數(shù)據(jù)。因此,當(dāng)切換Buffer到讀模式時,limit會被設(shè)置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數(shù)據(jù)(limit被設(shè)置成已寫數(shù)據(jù)的數(shù)量,這個值在寫模式下就是position)

Buffer的分配:

對Buffer對象的操作必須首先進行分配,Buffer提供一個allocate(int capacity)方法分配一個指定字節(jié)大小的對象。
向Buffer中寫數(shù)據(jù):寫數(shù)據(jù)到Buffer中有兩種方式:
1.從channel寫到Buffer

int bytes = channel.read(buf); //將channel中的數(shù)據(jù)讀取到buf中

2.通過Buffer的put()方法寫到Buffer

buf.put(byte); //將數(shù)據(jù)通過put()方法寫入到buf中
  • flip()方法:將Buffer從寫模式切換到讀模式,調(diào)用flip()方法會將position設(shè)置為0,并將limit設(shè)置為之前的position的值。
    從Buffer中讀數(shù)據(jù):從Buffer中讀數(shù)據(jù)有兩種方式:
    1.從Buffer讀取數(shù)據(jù)到Channel
int bytes = channel.write(buf); //將buf中的數(shù)據(jù)讀取到channel中

2.通過Buffer的get()方法讀取數(shù)據(jù)

byte bt = buf.get(); //從buf中讀取一個byte
  • rewind()方法:Buffer.rewind()方法將position設(shè)置為0,使得可以重讀Buffer中的所有數(shù)據(jù),limit保持不變。
  • clear()與compact()方法:一旦讀完Buffer中的數(shù)據(jù),需要讓Buffer準備好再次被寫入,可以通過clear()或compact()方法完成。如果調(diào)用的是clear()方法,position將被設(shè)置為0,limit設(shè)置為capacity的值。但是Buffer并未被清空,只是通過這些標(biāo)記告訴我們可以從哪里開始往Buffer中寫入多少數(shù)據(jù)。如果Buffer中還有一些未讀的數(shù)據(jù),調(diào)用clear()方法將被"遺忘 "。compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處,然后將position設(shè)置到最后一個未讀元素的后面,limit屬性依然設(shè)置為capacity??梢允沟肂uffer中的未讀數(shù)據(jù)還可以在后續(xù)中被使用。
  • mark()與reset()方法:通過調(diào)用Buffer.mark()方法可以標(biāo)記一個特定的position,之后可以通過調(diào)用Buffer.reset()恢復(fù)到這個position上。

Selector(多路復(fù)用器)

Selector與Channel是相互配合使用的,將Channel注冊在Selector上之后,才可以正確的使用Selector,但此時Channel必須為非阻塞模式。Selector可以監(jiān)聽Channel的四種狀態(tài)(Connect、Accept、Read、Write),當(dāng)監(jiān)聽到某一Channel的某個狀態(tài)時,才允許對Channel進行相應(yīng)的操作。

  • Connect:某一個客戶端連接成功后
  • Accept:準備好進行連接
  • Read:可讀
  • Write:可寫
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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