如何理解NIO
文章目錄
- 1.什么是NIO?
- 2.為什么用NIO,傳統(tǒng)IO有什么缺陷?
- 3.NIO和IO的區(qū)別
- 4.怎么理解NIO是面向塊的、非阻塞的
- 5.NIO是怎么實現(xiàn)的?
1.什么是NIO?
java.nio全稱java non-blocking IO(實際上是 new io),是指JDK 1.4 及以上版本里提供的新api(New IO) ,為所有的原始類型(boolean類型除外)提供緩存支持的數(shù)據(jù)容器,使用它可以提供非阻塞式的高伸縮性網(wǎng)絡(luò)。
2.為什么用NIO,傳統(tǒng)IO有什么缺陷?
一個使用傳統(tǒng)阻塞I/O的系統(tǒng),如果還是使用傳統(tǒng)的一個請求對應一個線程這種模式,一旦有高并發(fā)的大量請求,就會有如下問題:
1、線程不夠用, 就算使用了線程池復用線程也無濟于事;
2、阻塞I/O模式下,會有大量的線程被阻塞,一直在等待數(shù)據(jù),這個時候的線程被掛起,只能干等,CPU利用率很低,換句話說,系統(tǒng)的吞吐量差;
3、如果網(wǎng)絡(luò)I/O堵塞或者有網(wǎng)絡(luò)抖動或者網(wǎng)絡(luò)故障等,線程的阻塞時間可能很長。整個系統(tǒng)也變的不可靠
最直接的體現(xiàn)在于服務(wù)器與客戶端進行通訊時,每加入一臺客戶端需要一個IO線程阻塞等待對方數(shù)據(jù)傳送,會導致服務(wù)器不斷開啟線程,但這些線程大部分時間都是阻塞在那里,浪費資源,并且支持不了大并發(fā)。
3.NIO和IO的區(qū)別
原有的 IO 是面向流的、阻塞的,NIO 則是面向塊的、非阻塞的。
原始的IO是面向流的,不存在緩存的概念。Java IO面向流意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動流中的數(shù)據(jù)。如果需要前后移動從流中讀取的數(shù)據(jù),需要先將它緩存到一個緩沖區(qū)
Java IO的各種流是阻塞的,這意味著當一個線程調(diào)用read或 write方法時,該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入,該線程在此期間不能再干任何事情了。
4.怎么理解NIO是面向塊的、非阻塞的
NIO是面向緩沖區(qū)的。數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動,這就增加了處理過程中的靈活性。
Java NIO的非阻塞模式,使一個線程從某通道發(fā)送請求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時,就什么都不會獲取,而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此,一個線程請求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。
通俗理解:NIO是可以做到用一個線程來處理多個操作的。假設(shè)有10000個請求過來,根據(jù)實際情況,可以分配50或者100個線程來處理。不像之前的阻塞IO那樣,非得分配10000個。
5.NIO是怎么實現(xiàn)的?
1.buffer 緩存數(shù)組
緩沖區(qū)本質(zhì)上是一個可以寫入數(shù)據(jù)的內(nèi)存塊,然后可以再次讀取,該對象提供了一組方法,可以更輕松地使用內(nèi)存塊,使用緩沖區(qū)讀取和寫入數(shù)據(jù)通常遵循以下四個步驟:
當向buffer寫入數(shù)據(jù)時,buffer會記錄下寫了多少數(shù)據(jù),一旦要讀取數(shù)據(jù),需要通過flip()方法將Buffer從寫模式切換到讀模式,在讀模式下可以讀取之前寫入到buffer的所有數(shù)據(jù),一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫入。
三個重要概念(參數(shù))
a.容量(capacity)
b.界限(limit)
c.位置(position)
緩沖區(qū)常用的操作
向緩沖區(qū)寫數(shù)據(jù):
從緩沖區(qū)讀取數(shù)據(jù):
flip方法:
將Buffer從寫模式切換到讀模式,將position值重置為0,limit的值設(shè)置為之前position的值;
clear方法 vs compact方法:
clear方法清空緩沖區(qū);compact方法只會清空已讀取的數(shù)據(jù),而還未讀取的數(shù)據(jù)繼續(xù)保存在Buffer中;
2.channel
a. 通道可以同時進行讀寫,而流只能讀或者只能寫
b. 通道可以實現(xiàn)異步讀寫數(shù)據(jù)
c. 通道可以從緩沖讀數(shù)據(jù),也可以寫數(shù)據(jù)到緩沖:
channel提供了一個map()方法,可以直接將數(shù)據(jù)映射到內(nèi)存中。
3.Selector(選擇器)
可以檢測多個NIO channel,看看讀或者寫事件是否就緒。
多個Channel以事件的方式可以注冊到同一個Selector,從而達到用一個線程處理多個請求成為可能。
selector的創(chuàng)建
通過調(diào)用Selector.open()方法創(chuàng)建一個Selector對象
注冊Channel到Selector
channel.configureBlocking(false);
channel.register(selector, Selectionkey.OP_READ);
注:Channel必須是非阻塞的。
所以FileChannel不適用Selector,因為FileChannel不能切換為非阻塞模式,更準確的來說是因為FileChannel沒有繼承SelectableChannel。Socket channel可以正常使用。
register() 方法的第二個參數(shù)。這是一個“ interest集合 ”,意思是在通過Selector監(jiān)聽Channel時對什么事件感興趣。可以監(jiān)聽四種不同類型的事件:
- Connect
- Accept
- Read
- Write
SelectionKey介紹
一個SelectionKey鍵表示了一個特定的通道對象和一個特定的選擇器對象之間的注冊關(guān)系。
key.attachment(); //返回SelectionKey的attachment,attachment可以在注冊channel的時候指定。
key.channel(); // 返回該SelectionKey對應的channel。
key.selector(); // 返回該SelectionKey對應的Selector。
key.interestOps(); //返回代表需要Selector監(jiān)控的IO操作的bit mask
key.readyOps(); // 返回一個bit mask,代表在相應channel上可以進行的IO操作。
理解:
傳統(tǒng)IO在讀寫過程中是不允許停止的需要一直占用線程,對于一個一直有內(nèi)容可讀的文件不用切換線程這樣效率的確不錯,但是對于網(wǎng)絡(luò)通訊中網(wǎng)絡(luò)傳輸?shù)牟l(fā)需要不停開啟新的線程并且對于計算機而言,它的大部分時間都是阻塞的,所以對于這種情況我們就對它進行了改寫使用NIO非阻塞式的IO。
非阻塞式的IO,將內(nèi)容寫到一個Buffer中通過操作Bufer的標識(或者說游標),來改變其讀或者寫的狀態(tài),將它變成了分塊的模式,通過通道channel進行數(shù)據(jù)傳遞,對內(nèi)存中內(nèi)容進行映射,讀完了就結(jié)束,不需要一直占用線程。然而對于channel要有標識需要知道每個channel對應的誰寫的誰讀的問題,于是就有了Selector,通過regiser注冊每個channel這樣就可以知道哪個channel讀好了,寫完了的過程,執(zhí)行其他的操作,每個channel不是單獨的占用線程.這樣就更好的支持了并發(fā)。
總結(jié)
- 上一篇: Python 生成器(yield)
- 下一篇: Python 网络编程(Socket)