摘要:當(dāng)數(shù)據(jù)被寫(xiě)入到緩沖區(qū)時(shí),線(xiàn)程可以繼續(xù)處理它。當(dāng)滿(mǎn)足下列條件時(shí),表示兩個(gè)相等有相同的類(lèi)型等。調(diào)用通道的方法時(shí),可能會(huì)導(dǎo)致線(xiàn)程暫時(shí)阻塞,就算通道處于非阻塞模式也不例外。當(dāng)一個(gè)通道關(guān)閉時(shí),休眠在該通道上的所有線(xiàn)程都將被喚醒并收到一個(gè)異常。
1、NIO和I/O區(qū)別
I/O和NIO的區(qū)別在于數(shù)據(jù)的打包和傳輸方式。
I/O流的方式處理數(shù)據(jù)
NIO塊的方式處理數(shù)據(jù)
面向流 的 I/O 系統(tǒng)一次一個(gè)字節(jié)地處理數(shù)據(jù)。一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)的數(shù)據(jù),一個(gè)輸出流消費(fèi)一個(gè)字節(jié)的數(shù)據(jù)。為流式數(shù)據(jù)創(chuàng)建過(guò)濾器非常容易。鏈接幾個(gè)過(guò)濾器,以便每個(gè)過(guò)濾器只負(fù)責(zé)單個(gè)復(fù)雜處理機(jī)制的一部分,這樣也是相對(duì)簡(jiǎn)單的。面向流的 I/O 通常相當(dāng)慢。
一個(gè) 面向塊 的 I/O 系統(tǒng)以塊的形式處理數(shù)據(jù)。每一個(gè)操作都在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按(流式的)字節(jié)處理數(shù)據(jù)要快得多。但是面向塊的I/O比較復(fù)雜。NIO的主要應(yīng)用在高性能、高容量服務(wù)端應(yīng)用程序。
IO | NIO |
---|---|
面向流 | 面向塊,緩沖 |
阻塞IO | 非阻塞IO |
無(wú) | Selector |
1、Channels and Buffers(通道和緩沖區(qū))
標(biāo)準(zhǔn)的IO基于字節(jié)流和字符流進(jìn)行操作的,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫(xiě)入到通道中。
2、Non-blocking IO(非阻塞IO)
Java NIO可以非阻塞的方式使用IO,例如:當(dāng)線(xiàn)程從通道讀取數(shù)據(jù)到緩沖區(qū)時(shí),線(xiàn)程還是可以進(jìn)行其他事情。當(dāng)數(shù)據(jù)被寫(xiě)入到緩沖區(qū)時(shí),線(xiàn)程可以繼續(xù)處理它。從緩沖區(qū)寫(xiě)入通道也類(lèi)似。
3、Selectors(選擇器)
Java NIO引入了選擇器的概念,選擇器用于監(jiān)聽(tīng)多個(gè)通道的事件(比如:連接打開(kāi),數(shù)據(jù)到達(dá))。因此,單個(gè)的線(xiàn)程可以監(jiān)聽(tīng)多個(gè)數(shù)據(jù)通道。
通道和緩沖區(qū)是 NIO 中的核心對(duì)象,幾乎在每一個(gè) I/O 操作中都要使用它們。
一個(gè) Buffer 實(shí)質(zhì)上是一個(gè)容器對(duì)象,它包含一些要寫(xiě)入或者剛讀出的數(shù)據(jù)。
在 NIO 庫(kù)中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的。在寫(xiě)入數(shù)據(jù)時(shí),它是寫(xiě)入到緩沖區(qū)中的。任何時(shí)候訪(fǎng)問(wèn) NIO 中的數(shù)據(jù),您都是將它放到緩沖區(qū)中。
緩沖區(qū)實(shí)質(zhì)上就是一個(gè)數(shù)組,通常它是一個(gè)字節(jié)數(shù)組,但是也可以使用其他種類(lèi)的數(shù)組。但它不僅僅是一個(gè)數(shù)組,緩沖區(qū)還提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪(fǎng)問(wèn),而且還可以跟蹤系統(tǒng)的讀/寫(xiě)進(jìn)程。
Buffer 類(lèi)型最常用的緩沖區(qū)類(lèi)型是 ByteBuffer。一個(gè) ByteBuffer 可以在其底層字節(jié)數(shù)組上進(jìn)行 get/set 操作(即字節(jié)的獲取和設(shè)置)。
ByteBuffer 不是 NIO 中唯一的緩沖區(qū)類(lèi)型。事實(shí)上,對(duì)于每一種基本 Java 類(lèi)型都有一種緩沖區(qū)類(lèi)型:
Buffer抽象類(lèi)中提供了需要處理的方法類(lèi)型。
Buffer 用法使用Buffer讀寫(xiě)數(shù)據(jù)一般遵循以下四個(gè)步驟:
寫(xiě)入數(shù)據(jù)到Buffer
調(diào)用flip()方法
從Buffer中讀取數(shù)據(jù)
調(diào)用clear()方法或者compact()方法
當(dāng)向buffer寫(xiě)入數(shù)據(jù)時(shí),buffer會(huì)記錄下寫(xiě)了多少數(shù)據(jù)。一旦要讀取數(shù)據(jù),需要通過(guò)flip()方法將Buffer從寫(xiě)模式切換到讀模式。在讀模式下,可以讀取之前寫(xiě)入到buffer的所有數(shù)據(jù)。一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫(xiě)入。
有兩種方式能清空緩沖區(qū):調(diào)用clear()或compact()方法。
clear()方法會(huì)清空整個(gè)緩沖區(qū)。compact()方法只會(huì)清除已經(jīng)讀過(guò)的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫(xiě)入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。
try (RandomAccessFile aFile = new RandomAccessFile("F:1.txt", "rw")) { //從RandomAccesFile獲取通道 FileChannel inChannel = aFile.getChannel(); //創(chuàng)建一個(gè)緩沖區(qū)并在其中放入一些數(shù)據(jù) ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf); //read into buffer. //檢查狀態(tài) while (bytesRead != -1) { //flip() 方法讓緩沖區(qū)可以將新讀入的數(shù)據(jù)寫(xiě)入另一個(gè)通道。 buf.flip(); //make buffer ready for read while (buf.hasRemaining()) { System.out.print((char) buf.get()); // read 1 byte at a time } buf.clear(); //make buffer ready for writing bytesRead = inChannel.read(buf); } }capacity & position & limit
緩沖區(qū)本質(zhì)上是一塊可以寫(xiě)入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝成NIO Buffer對(duì)象,并提供了一組方法,用來(lái)方便的訪(fǎng)問(wèn)該塊內(nèi)存。
緩沖區(qū)對(duì)象有三個(gè)基本屬性:
容量Capacity:緩沖區(qū)能容納的數(shù)據(jù)元素的最大數(shù)量,在緩沖區(qū)創(chuàng)建時(shí)設(shè)定,無(wú)法更改
上界Limit:表明還有多少數(shù)據(jù)需要取出(在從緩沖區(qū)寫(xiě)入通道時(shí)),或者還有多少空間可以放入數(shù)據(jù)(在從通道讀入緩沖區(qū)時(shí)),limit不能大于capacity
位置Position:下一個(gè)要被讀或?qū)懙脑氐乃饕?/p>
這三個(gè)變量一起可以跟蹤緩沖區(qū)的狀態(tài)和它所包含的數(shù)據(jù)。
四個(gè)屬性總是遵循這樣的關(guān)系:0<=mark<=position<=limit<=capacity。下圖是新創(chuàng)建的容量為10的緩沖區(qū)邏輯視圖:
寫(xiě)入模式
buffer.put((byte)"H").put((byte)"e").put((byte)"l").put((byte)"l").put((byte)"o");
五次調(diào)用put后的緩沖區(qū):
此時(shí),limit表示還有多少空間可以放入數(shù)據(jù)。position最大可為capacity-1。
讀取模式
現(xiàn)在緩沖區(qū)滿(mǎn)了,我們必須將其清空。我們想把這個(gè)緩沖區(qū)傳遞給一個(gè)通道,以使內(nèi)容能被全部寫(xiě)出,但現(xiàn)在執(zhí)行g(shù)et()無(wú)疑會(huì)取出未定義的數(shù)據(jù)。我們必須將posistion設(shè)為0,然后通道就會(huì)從正確的位置開(kāi)始讀了,但讀到哪算讀完了呢?這正是limit引入的原因,它指明緩沖區(qū)有效內(nèi)容的未端。這個(gè)操作 在緩沖區(qū)中叫做翻轉(zhuǎn):buffer.flip()。
flip這個(gè)方法做兩件非常重要的事:
將 limit 設(shè)置為當(dāng)前 position。
將 position 設(shè)置為 0。
rewind操作與flip相似,但不影響limit。
clear
最后一步是調(diào)用緩沖區(qū)的 clear() 方法。這個(gè)方法重設(shè)緩沖區(qū)以便接收更多的字節(jié)。
Clear 做兩種非常重要的事情:
1.將 limit 設(shè)置為與 capacity 相同。
2.設(shè)置 position 為 0。
Buffer的分配clear()與compact()區(qū)別
1、clear()方法,position將被設(shè)回0,limit被設(shè)置成 capacity的值。Buffer中有一些未讀的數(shù)據(jù),調(diào)用clear()方法,數(shù)據(jù)將“被遺忘”
2、compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設(shè)到最后一個(gè)未讀元素正后面。limit屬性依然像clear()方法一樣,設(shè)置成capacity。
在能夠讀和寫(xiě)之前,必須有一個(gè)緩沖區(qū)。要?jiǎng)?chuàng)建緩沖區(qū),必須要進(jìn)行分配,。我們使用靜態(tài)方法 allocate() 來(lái)分配緩沖區(qū),每一個(gè)Buffer類(lèi)都有一個(gè)allocate方法。
//分配48字節(jié)capacity的ByteBuffer的例子。 ByteBuffer buf = ByteBuffer.allocate(48); //分配一個(gè)可存儲(chǔ)1024個(gè)字符的CharBuffer: CharBuffer buf = CharBuffer.allocate(1024);Buffer寫(xiě)入
寫(xiě)數(shù)據(jù)到Buffer有兩種方式:
從Channel寫(xiě)到Buffer。
通過(guò)Buffer的put()方法寫(xiě)到Buffer里。
//從Channel寫(xiě)到Buffer int bytesRead = inChannel.read(buf); //read into buffer. //通過(guò)put方法寫(xiě)B(tài)uffer的例子: buf.put(127);Buffer讀取
從Buffer中讀取數(shù)據(jù)有兩種方式:
從Buffer讀取數(shù)據(jù)到Channel。
使用get()方法從Buffer中讀取數(shù)據(jù)。
//read from buffer into channel. int bytesWritten = inChannel.write(buf); //使用get()方法從Buffer中讀取數(shù)據(jù) byte aByte = buf.get();
get方法有很多版本,允許你以不同的方式從Buffer中讀取數(shù)據(jù)。例如,從指定position讀取,或者從Buffer中讀取數(shù)據(jù)到字節(jié)數(shù)組。
rewind()方法
Buffer.rewind()將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變,仍然表示能從Buffer中讀取多少個(gè)元素(byte、char等)。
mark()與reset()
通過(guò)調(diào)用Buffer.mark()方法,可以標(biāo)記Buffer中的一個(gè)特定position。之后可以通過(guò)調(diào)用Buffer.reset()方法恢復(fù)到這個(gè)position。
Buffer比較equals()與compareTo()方法
可以使用equals()和compareTo()方法兩個(gè)Buffer。
equals()
當(dāng)滿(mǎn)足下列條件時(shí),表示兩個(gè)Buffer相等:
有相同的類(lèi)型(byte、char、int等)。
Buffer中剩余的byte、char等的個(gè)數(shù)相等。
Buffer中所有剩余的byte、char等都相同。
equals只是比較Buffer的一部分,不是每一個(gè)在它里面的元素都比較。實(shí)際上,它只比較Buffer中的剩余元素。
compareTo()
compareTo()方法比較兩個(gè)Buffer的剩余元素(byte、char等), 如果滿(mǎn)足下列條件,則認(rèn)為一個(gè)Buffer“小于”另一個(gè)Buffer:
第一個(gè)不相等的元素小于另一個(gè)Buffer中對(duì)應(yīng)的元素
第一個(gè)Buffer的元素個(gè)數(shù)比另一個(gè)少
3、ChannelJava NIO的通道類(lèi)似流,但又有些不同:
既可以從通道中讀取數(shù)據(jù),又可以寫(xiě)數(shù)據(jù)到通道。但流的讀寫(xiě)通常是單向的。
通道可以異步地讀寫(xiě)。
通道中的數(shù)據(jù)總是要先讀到一個(gè)Buffer,或者總是要從一個(gè)Buffer中寫(xiě)入。
Channel類(lèi)型FileChannel 從文件中讀寫(xiě)數(shù)據(jù)。
DatagramChannel 能通過(guò)UDP讀寫(xiě)網(wǎng)絡(luò)中的數(shù)據(jù)。
SocketChannel 能通過(guò)TCP讀寫(xiě)網(wǎng)絡(luò)中的數(shù)據(jù)。
ServerSocketChannel可以監(jiān)聽(tīng)新進(jìn)來(lái)的TCP連接,像Web服務(wù)器那樣。對(duì)每一個(gè)新進(jìn)來(lái)的連接都會(huì)創(chuàng)建一個(gè)SocketChannel。
close Channel與緩沖區(qū)不同,通道不能被重復(fù)使用;關(guān)閉通道后,通道將不再連接任何東西,任何的讀或?qū)懖僮鞫紩?huì)導(dǎo)致ClosedChannelException。
調(diào)用通道的close()方法時(shí),可能會(huì)導(dǎo)致線(xiàn)程暫時(shí)阻塞,就算通道處于非阻塞模式也不例外。如果通道實(shí)現(xiàn)了InterruptibleChannel接 口,那么阻塞在該通道上的一個(gè)線(xiàn)程被中斷時(shí),該通道將被關(guān)閉,被阻塞線(xiàn)程也會(huì)拋出ClosedByInterruptException異常。
當(dāng)一個(gè)通道 關(guān)閉時(shí),休眠在該通道上的所有線(xiàn)程都將被喚醒并收到一個(gè)AsynchronousCloseException異常。
Scatter/Gatherscatter/gather用于描述從Channel中讀取或者寫(xiě)入到Channel的操作。
分散(scatter)從Channel中讀取是指在讀操作時(shí)將讀取的數(shù)據(jù)寫(xiě)入多個(gè)buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)"分散(scatter)"到多個(gè)Buffer中。
聚集(gather)寫(xiě)入Channel是指在寫(xiě)操作時(shí)將多個(gè)buffer的數(shù)據(jù)寫(xiě)入同一個(gè)Channel,因此,Channel 將多個(gè)Buffer中的數(shù)據(jù)"聚集(gather)"后發(fā)送到Channel。
scatter / gather經(jīng)常用于需要將傳輸?shù)臄?shù)據(jù)分開(kāi)處理的場(chǎng)合,例如傳輸一個(gè)由消息頭和消息體組成的消息,你可能會(huì)將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體。
Scatter ReaderByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); ByteBuffer[] bufferArray = { header, body }; channel.read(bufferArray);
buffer首先被插入到數(shù)組,然后再將數(shù)組作為channel.read() 的輸入?yún)?shù)。read()方法按照buffer在數(shù)組中的順序?qū)腸hannel中讀取的數(shù)據(jù)寫(xiě)入到buffer,當(dāng)一個(gè)buffer被寫(xiě)滿(mǎn)后,channel緊接著向另一個(gè)buffer中寫(xiě)。
Scattering Reads在移動(dòng)下一個(gè)buffer前,必須填滿(mǎn)當(dāng)前的buffer,這也意味著它不適用于動(dòng)態(tài)消息(譯者注:消息大小不固定)。換句話(huà)說(shuō),如果存在消息頭和消息體,消息頭必須完成填充(例如 128byte),Scattering Reads才能正常工作。
Gathering WritesGathering Writes是指數(shù)據(jù)從多個(gè)buffer寫(xiě)入到同一個(gè)channel。如下圖描述:
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); //write data into buffers ByteBuffer[] bufferArray = { header, body }; channel.write(bufferArray);
buffers數(shù)組是write()方法的入?yún)?,write()方法會(huì)按照buffer在數(shù)組中的順序,將數(shù)據(jù)寫(xiě)入到channel,注意只有position和limit之間的數(shù)據(jù)才會(huì)被寫(xiě)入。因此,如果一個(gè)buffer的容量為128byte,但是僅僅包含58byte的數(shù)據(jù),那么這58byte的數(shù)據(jù)將被寫(xiě)入到channel中。因此與Scattering Reads相反,Gathering Writes能較好的處理動(dòng)態(tài)消息。
FileChannelJava NIO中的FileChannel是一個(gè)連接到文件的通道??梢酝ㄟ^(guò)文件通道讀寫(xiě)文件。FileChannel無(wú)法設(shè)置為非阻塞模式,它總是運(yùn)行在阻塞模式下。
FileChannel讀數(shù)據(jù)因?yàn)闊o(wú)法保證write()方法一次能向FileChannel寫(xiě)入多少字節(jié),因此需要重復(fù)調(diào)用write()方法,直到Buffer中已經(jīng)沒(méi)有尚未寫(xiě)入通道的字節(jié)。
//通過(guò)inputstream或者RandomAccessFile,打開(kāi)FileChannel RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); FileChannel inChannel = aFile.getChannel(); //往buffer里面讀取數(shù)據(jù) ByteBuffer buf = ByteBuffer.allocate(48); //int值表示了有多少字節(jié)被讀到了Buffer中。如果返回-1,表示到了文件末尾。 int bytesRead = inChannel.read(buf);FileChannel寫(xiě)數(shù)據(jù)
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { channel.write(buf); } //用完FileChannel后必須將其關(guān)閉 channel.close();4、Selector
Selector(選擇器)是Java NIO中能夠檢測(cè)一到多個(gè)NIO通道,并能夠知曉通道是否為諸如讀寫(xiě)事件做好準(zhǔn)備的組件。這樣,一個(gè)多帶帶的線(xiàn)程可以管理多個(gè)channel,從而管理多個(gè)網(wǎng)絡(luò)連接。
對(duì)于操作系統(tǒng)來(lái)說(shuō),線(xiàn)程之間上下文切換的開(kāi)銷(xiāo)很大,而且每個(gè)線(xiàn)程都要占用系統(tǒng)的一些資源(如內(nèi)存)。因此,使用的線(xiàn)程越少越好。
Selector的創(chuàng)建與注冊(cè)使用Selector.open()方法創(chuàng)建Selector,為了將Channel和Selector配合使用,必須將channel注冊(cè)到selector上。通過(guò)SelectableChannel.register()方法來(lái)實(shí)現(xiàn)。如下所示
//創(chuàng)建Selector Selector selector = Selector.open(); //向Selector注冊(cè)通道 channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
與Selector一起使用時(shí),Channel必須處于非阻塞模式下。這意味著不能將FileChannel與Selector一起使用,因?yàn)镕ileChannel不能切換到非阻塞模式。而套接字通道都可以。
register()方法的第二個(gè)參數(shù)。這是一個(gè)“interest集合”,意思是在通過(guò)Selector監(jiān)聽(tīng)Channel時(shí)對(duì)什么事件感興趣??梢员O(jiān)聽(tīng)四種不同類(lèi)型的事件:
事件類(lèi)型 | 常量 |
---|---|
Connect | SelectionKey.OP_CONNECT |
Accept | SelectionKey.OP_ACCEPT |
Read | SelectionKey.OP_READ |
Write | SelectionKey.OP_WRITE |
通道觸發(fā)了一個(gè)事件意思是該事件已經(jīng)就緒。所以,某個(gè)channel成功連接到另一個(gè)服務(wù)器稱(chēng)為“連接就緒”。一個(gè)server socket channel準(zhǔn)備好接收新進(jìn)入的連接稱(chēng)為“接收就緒”。一個(gè)有數(shù)據(jù)可讀的通道可以說(shuō)是“讀就緒”。等待寫(xiě)數(shù)據(jù)的通道可以說(shuō)是“寫(xiě)就緒”。
SelectionKey當(dāng)向Selector注冊(cè)Channel時(shí),register()方法會(huì)返回一個(gè)SelectionKey對(duì)象。這個(gè)對(duì)象包含了一些屬性:
interest集合
ready集合
Channel
Selector
附加的對(duì)象
interest集合
interest集合是你所選擇的感興趣的事件集合??梢酝ㄟ^(guò)SelectionKey讀寫(xiě)interest集合,像這樣:
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
用|位與操作interest 集合和給定的SelectionKey常量,可以確定某個(gè)確定的事件是否在interest 集合中。
ready集合
ready 集合是通道已經(jīng)準(zhǔn)備就緒的操作的集合。在一次選擇(Selection)之后,會(huì)首先訪(fǎng)問(wèn)這個(gè)ready set??梢赃@樣訪(fǎng)問(wèn)ready集合:
int readySet = selectionKey.readyOps(); selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();
可以用像檢測(cè)interest集合那樣的方法,來(lái)檢測(cè)channel中什么事件或操作已經(jīng)就緒。但是,也可以使用以上四個(gè)方法,它們都會(huì)返回一個(gè)布爾類(lèi)型。
Channel + Selector
從SelectionKey訪(fǎng)問(wèn)Channel和Selector很簡(jiǎn)單。如下:
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
附加的對(duì)象
可以將一個(gè)對(duì)象或者更多信息附著到SelectionKey上,可以方便識(shí)別某個(gè)給定的通道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集數(shù)據(jù)的某個(gè)對(duì)象。使用方法如下:
selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment(); //用register()方法向Selector注冊(cè)Channel的時(shí)候附加對(duì)象 SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);Selector選擇通道
一旦向Selector注冊(cè)了一或多個(gè)通道,就可以調(diào)用select()方法,選擇已經(jīng)準(zhǔn)備就緒的事件的通道類(lèi)型。
select方法
int select() //阻塞到至少有一個(gè)通道在你注冊(cè)的事件上就緒了 int select(long timeout)//和select()一樣,除了最長(zhǎng)會(huì)阻塞timeout毫秒(參數(shù))。 int selectNow()//不會(huì)阻塞,不管什么通道就緒都立刻返回
select()方法返回的int值表示有多少通道已經(jīng)就緒,然后可以通過(guò)調(diào)用selector的selectedKeys()方法,訪(fǎng)問(wèn)“已選擇鍵集(selected key set)”中的就緒通道。如下所示:
//訪(fǎng)問(wèn)“已選擇鍵集(selected key set)”中的就緒通道 Set selectedKeys = selector.selectedKeys(); Iterator keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if(key.isAcceptable()) { // a connection was accepted by a ServerSocketChannel. } else if (key.isConnectable()) { // a connection was established with a remote server. } else if (key.isReadable()) { // a channel is ready for reading } else if (key.isWritable()) { // a channel is ready for writing } keyIterator.remove(); }
這個(gè)循環(huán)遍歷已選擇鍵集中的每個(gè)鍵,并檢測(cè)各個(gè)鍵所對(duì)應(yīng)的通道的就緒事件。
注意每次迭代末尾的keyIterator.remove()調(diào)用。Selector不會(huì)自己從已選擇鍵集中移除SelectionKey實(shí)例。必須在處理完通道時(shí)自己移除。下次該通道變成就緒時(shí),Selector會(huì)再次將其放入已選擇鍵集中。
wakeUp()
某個(gè)線(xiàn)程調(diào)用select()方法后阻塞了,即使沒(méi)有通道已經(jīng)就緒,也有辦法讓其從select()方法返回。只要讓其它線(xiàn)程在第一個(gè)線(xiàn)程調(diào)用select()方法的那個(gè)對(duì)象上調(diào)用Selector.wakeup()方法即可。阻塞在select()方法上的線(xiàn)程會(huì)立馬返回。
如果有其它線(xiàn)程調(diào)用了wakeup()方法,但當(dāng)前沒(méi)有線(xiàn)程阻塞在select()方法上,下個(gè)調(diào)用select()方法的線(xiàn)程會(huì)立即“醒來(lái)(wake up)”。
close()
用完Selector后調(diào)用其close()方法會(huì)關(guān)閉該Selector,且使注冊(cè)到該Selector上的所有SelectionKey實(shí)例無(wú)效。通道本身并不會(huì)關(guān)閉。
5、 管道(pip)管道是2個(gè)線(xiàn)程之間的單向數(shù)據(jù)連接。Pipe有一個(gè)source通道和一個(gè)sink通道。數(shù)據(jù)會(huì)被寫(xiě)到sink通道,從source通道讀取。
原理圖如下:
//Pipe.open()方法打開(kāi)管道 Pipe pipe = Pipe.open(); //訪(fǎng)問(wèn)sink通道,向管道寫(xiě)數(shù)據(jù) Pipe.SinkChannel sinkChannel = pipe.sink(); //調(diào)用SinkChannel的write()方法,將數(shù)據(jù)寫(xiě)入SinkChannel String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()) { sinkChannel.write(buf); }管道讀取數(shù)據(jù)
//訪(fǎng)問(wèn)source通道 Pipe.SourceChannel sourceChannel = pipe.source(); //調(diào)用source通道的read()方法來(lái)讀取數(shù)據(jù) ByteBuffer buf = ByteBuffer.allocate(48); //read()方法返回的int值表示多少字節(jié)被讀進(jìn)了緩沖區(qū) int bytesRead = sourceChannel.read(buf);
參考資料引用
1、NIO 入門(mén)
2、Java NIO教程
3、理解Java NIO
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66975.html
摘要:從通道進(jìn)行數(shù)據(jù)寫(xiě)入創(chuàng)建一個(gè)緩沖區(qū),填充數(shù)據(jù),并要求通道寫(xiě)入數(shù)據(jù)。三之通道主要內(nèi)容通道介紹通常來(lái)說(shuō)中的所有都是從通道開(kāi)始的。從中選擇選擇器維護(hù)注冊(cè)過(guò)的通道的集合,并且這種注冊(cè)關(guān)系都被封裝在當(dāng)中停止選擇的方法方法和方法。 由于內(nèi)容比較多,我下面放的一部分是我更新在我的微信公眾號(hào)上的鏈接,微信排版比較好看,更加利于閱讀。每一篇文章下面我都把文章的主要內(nèi)容給列出來(lái)了,便于大家學(xué)習(xí)與回顧。 Ja...
摘要:的選擇器允許單個(gè)線(xiàn)程監(jiān)視多個(gè)輸入通道。一旦執(zhí)行的線(xiàn)程已經(jīng)超過(guò)讀取代碼中的某個(gè)數(shù)據(jù)片段,該線(xiàn)程就不會(huì)在數(shù)據(jù)中向后移動(dòng)通常不會(huì)。 1、引言 很多初涉網(wǎng)絡(luò)編程的程序員,在研究Java NIO(即異步IO)和經(jīng)典IO(也就是常說(shuō)的阻塞式IO)的API時(shí),很快就會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題:我什么時(shí)候應(yīng)該使用經(jīng)典IO,什么時(shí)候應(yīng)該使用NIO? 在本文中,將嘗試用簡(jiǎn)明扼要的文字,闡明Java NIO和經(jīng)典IO之...
摘要:而我們現(xiàn)在都已經(jīng)發(fā)布了,的都不知道,這有點(diǎn)說(shuō)不過(guò)去了。而對(duì)一個(gè)的讀寫(xiě)也會(huì)有響應(yīng)的描述符,稱(chēng)為文件描述符,描述符就是一個(gè)數(shù)字,指向內(nèi)核中的一個(gè)結(jié)構(gòu)體文件路徑,數(shù)據(jù)區(qū)等一些屬性。 前言 只有光頭才能變強(qiáng) 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡(jiǎn)單啦 本來(lái)我預(yù)想是先來(lái)回顧一下傳統(tǒng)的IO模式的,將傳統(tǒng)的IO模式的相關(guān)類(lèi)理清楚(因?yàn)镮O的類(lèi)很多)。 但是,發(fā)現(xiàn)在整理的過(guò)程已...
摘要:上篇說(shuō)了最基礎(chǔ)的五種模型,相信大家對(duì)相關(guān)的概念應(yīng)該有了一定的了解,這篇文章主要講講基于多路復(fù)用的。 上篇說(shuō)了最基礎(chǔ)的五種IO模型,相信大家對(duì)IO相關(guān)的概念應(yīng)該有了一定的了解,這篇文章主要講講基于多路復(fù)用IO的Java NIO。 背景 Java誕生至今,有好多種IO模型,從最早的Java IO到后來(lái)的Java NIO以及最新的Java AIO,每種IO模型都有它自己的特點(diǎn),詳情請(qǐng)看我的上...
摘要:的出現(xiàn)解決了這尷尬的問(wèn)題,非阻塞模式下,通過(guò),我們的線(xiàn)程只為已就緒的通道工作,不用盲目的重試了。注意要將注冊(cè)到,首先需要將設(shè)置為非阻塞模式,否則會(huì)拋異常。 showImg(https://segmentfault.com/img/remote/1460000017053374); 背景知識(shí) 同步、異步、阻塞、非阻塞 首先,這幾個(gè)概念非常容易搞混淆,但NIO中又有涉及,所以總結(jié)一下。 ...
閱讀 2687·2021-11-16 11:53
閱讀 2750·2021-07-26 23:38
閱讀 2081·2019-08-30 15:55
閱讀 1763·2019-08-30 13:21
閱讀 3686·2019-08-29 17:26
閱讀 3316·2019-08-29 13:20
閱讀 884·2019-08-29 12:20
閱讀 3204·2019-08-26 10:21