目錄:
Java NIO 學(xué)習(xí)筆記(一)----概述,Channel/Buffer
Java NIO 學(xué)習(xí)筆記(二)----聚集和分散,通道到通道
Java NIO 學(xué)習(xí)筆記(三)----Selector
Java NIO 學(xué)習(xí)筆記(四)----文件通道和網(wǎng)絡(luò)通道
Java NIO 學(xué)習(xí)筆記(五)----路徑、文件和管道 Path/Files/Pipe
Java NIO 學(xué)習(xí)筆記(六)----異步文件通道 AsynchronousFileChannel
Java NIO 學(xué)習(xí)筆記(七)----NIO/IO 的對(duì)比和總結(jié)
Java NIO (來(lái)自 Java 1.4)可以替代標(biāo)準(zhǔn) IO 和 Java Networking API ,NIO 提供了與標(biāo)準(zhǔn) IO 不同的使用方式。學(xué)習(xí) NIO 之前建議先掌握標(biāo)準(zhǔn) IO 和 Java 網(wǎng)絡(luò)編程,推薦教程:
本文目的: 掌握了標(biāo)準(zhǔn) IO 之后繼續(xù)學(xué)習(xí) NIO 知識(shí)。主要參考 JavaDoc 和 Jakob Jenkov 的英文教程 Java NIO Tutorial
Java NIO 概覽
NIO 由以下核心組件組成:
通道和緩沖區(qū)
在標(biāo)準(zhǔn) IO API 中,使用字節(jié)流和字符流。 在 NIO 中使用通道和緩沖區(qū)。 數(shù)據(jù)總是從通道讀入緩沖區(qū),或從緩沖區(qū)寫入通道。非阻塞IO
NIO 可以執(zhí)行非阻塞 IO 。 例如,當(dāng)通道將數(shù)據(jù)讀入緩沖區(qū)時(shí),線程可以執(zhí)行其他操作。 并且一旦數(shù)據(jù)被讀入緩沖區(qū),線程就可以繼續(xù)處理它。 將數(shù)據(jù)寫入通道也是如此。選擇器
NIO 包含“選擇器”的概念。 選擇器是一個(gè)可以監(jiān)視多個(gè)事件通道的對(duì)象(例如:連接打開,數(shù)據(jù)到達(dá)等)。 因此,單個(gè)線程可以監(jiān)視多個(gè)通道的數(shù)據(jù)。
NIO 有比這些更多的類和組件,但在我看來(lái),Channel,Buffer 和 Selector 構(gòu)成了 API 的核心。 其余的組件,如 Pipe 和 FileLock ,只是與三個(gè)核心組件一起使用的實(shí)用程序類。
Channels/Buffers 通道和緩沖區(qū)
通常,NIO 中的所有 IO 都以 Channel 開頭,頻道有點(diǎn)像流。 數(shù)據(jù)可以從 Channel 讀入 Buffer,也可以從 Buffer 寫入 Channel :

有幾種 Channel 和 Buffer ,以下是 NIO 中主要 Channel 實(shí)現(xiàn)類的列表,這些通道包括 UDP + TCP 網(wǎng)絡(luò) IO 和文件 IO:
- FileChannel :文件通道
- DatagramChannel :數(shù)據(jù)報(bào)通道
- SocketChannel :套接字通道
- ServerSocketChannel :服務(wù)器套接字通道
這些類也有一些有趣的接口,但為了簡(jiǎn)單起見(jiàn),這里暫時(shí)不提,后續(xù)會(huì)進(jìn)行學(xué)習(xí)的。
以下是 NIO 中的核心 Buffer 實(shí)現(xiàn),其實(shí)就是 7 種基本類型:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
NIO 還有一個(gè) MappedByteBuffer,它與內(nèi)存映射文件一起使用,同樣這個(gè)后續(xù)再講。
Selectors 選擇器
選擇器允許單個(gè)線程處理多個(gè)通道。 如果程序打開了許多連接(通道),但每個(gè)連接只有較低的流量,使用選擇器就很方便。 例如,在聊天服務(wù)器中, 以下是使用 Selector 處理 3 個(gè) Channel 的線程圖示:

要使用選擇器,需要使用它注冊(cè)通道。 然后你調(diào)用它的 select() 方法。 此方法將阻塞,直到有一個(gè)已注冊(cè)通道的事件準(zhǔn)備就緒。 一旦該方法返回,該線程就可以處理這些事件。 事件可以是傳入連接,接收數(shù)據(jù)等。
Channel (通道)
NIO 通道類似于流,但有一些區(qū)別:
- 通道可以讀取和寫入。 流通常是單向的(讀或?qū)懀?/li>
- 通道可以異步讀取和寫入。
- 通道始終讀取或?qū)懭刖彌_區(qū),即它只面向緩沖區(qū)。
如上所述,NIO 中總是將數(shù)據(jù)從通道讀取到緩沖區(qū),或?qū)?shù)據(jù)從緩沖區(qū)寫入通道。 這是一個(gè)例子:
// 文件內(nèi)容是 123456789
RandomAccessFile accessFile = new RandomAccessFile("D:\\test\\1.txt", "rw");
FileChannel fileChannel = accessFile.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
int data = fileChannel.read(buffer); // 將 Channel 的數(shù)據(jù)讀入緩沖區(qū),返回讀入到緩沖區(qū)的字節(jié)數(shù)
Buffer(緩沖區(qū))
使用 Buffer 與 Channel 交互,數(shù)據(jù)從通道讀入緩沖區(qū),或從緩沖區(qū)寫入通道。
緩沖區(qū)本質(zhì)上是一個(gè)可以寫入數(shù)據(jù)的內(nèi)存塊,之后可以讀取數(shù)據(jù)。 Buffer 對(duì)象包裝了此內(nèi)存塊,提供了一組方法,可以更輕松地使用內(nèi)存塊。
Buffer 的基本用法
使用 Buffer 讀取和寫入數(shù)據(jù)通常遵循以下四個(gè)步驟:
- 將數(shù)據(jù)寫入緩沖區(qū)
- 調(diào)用 buffer.flip() 反轉(zhuǎn)讀寫模式
- 從緩沖區(qū)讀取數(shù)據(jù)
- 調(diào)用 buffer.clear() 或 buffer.compact() 清除緩沖區(qū)內(nèi)容
將數(shù)據(jù)寫入Buffer 時(shí),Buffer 會(huì)跟蹤寫入的數(shù)據(jù)量。 當(dāng)需要讀取數(shù)據(jù)時(shí),就使用 flip() 方法將緩沖區(qū)從寫入模式切換到讀取模式。 在讀取模式下,緩沖區(qū)允許讀取寫入緩沖區(qū)的所有數(shù)據(jù)。
讀完所有數(shù)據(jù)之后,就需要清除緩沖區(qū),以便再次寫入。 可以通過(guò)兩種方式執(zhí)行此操作:通過(guò)調(diào)用 clear() 或調(diào)用 compact() 。區(qū)別在于 clear() 是方法清除整個(gè)緩沖區(qū),而 compact() 方法僅清除已讀取的數(shù)據(jù),未讀數(shù)據(jù)都會(huì)移動(dòng)到緩沖區(qū)的開頭,新數(shù)據(jù)將在未讀數(shù)據(jù)之后寫入緩沖區(qū)。
這是一個(gè)簡(jiǎn)單的緩沖區(qū)用法示例:
public class ChannelExample {
public static void main(String[] args) throws IOException {
// 文件內(nèi)容是 123456789
RandomAccessFile accessFile = new RandomAccessFile("D:\\test\\1.txt", "rw");
FileChannel fileChannel = accessFile.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48); //創(chuàng)建容量為48字節(jié)的緩沖區(qū)
int data = fileChannel.read(buffer); // 將 Channel 的數(shù)據(jù)讀入緩沖區(qū),返回讀入到緩沖區(qū)的字節(jié)數(shù)
while (data != -1) {
System.out.println("Read " + data); // Read 9
buffer.flip(); // 將 buffer 從寫入模式切換為讀取模式
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get()); // 每次讀取1byte,循環(huán)輸出 123456789
}
buffer.clear(); // 清除當(dāng)前緩沖區(qū)
data = fileChannel.read(buffer); // 將 Channel 的數(shù)據(jù)讀入緩沖區(qū)
}
accessFile.close();
}
}
Buffer 的 capacity,position 和 limit
緩沖區(qū)有 3 個(gè)需要熟悉的屬性,以便了解緩沖區(qū)的工作原理。 這些是:
- capacity : 容量緩沖區(qū)的容量,是它所包含的元素的數(shù)量。不能為負(fù)并且不能更改。
- position :緩沖區(qū)的位置 是下一個(gè)要讀取或?qū)懭氲脑氐乃饕?。不能為?fù),并且不能大于 limit
- limit : 緩沖區(qū)的限制,緩沖區(qū)的限制不能為負(fù),并且不能大于 capacity
另外還有標(biāo)記 mark ,
標(biāo)記、位置、限制和容量值遵守以下不變式:
0 <= mark<= position <= limit<= capacity
position 和 limit 的含義取決于 Buffer 是處于讀取還是寫入模式。 無(wú)論緩沖模式如何,capacity 總是一樣的表示容量。
以下是寫入和讀取模式下的容量,位置和限制的說(shuō)明:

capacity
作為存儲(chǔ)器塊,緩沖區(qū)具有一定的固定大小,也稱為“容量”。 只能將 capacity 多的 byte,long,char 等寫入緩沖區(qū)。 緩沖區(qū)已滿后,需要清空它(讀取數(shù)據(jù)或清除它),然后才能將更多數(shù)據(jù)寫入。
position
將數(shù)據(jù)寫入緩沖區(qū)時(shí),可以在某個(gè)位置執(zhí)行操作。 position? 初始值為 ?0 ,當(dāng)一個(gè) byte,long,char 等已寫入緩沖區(qū)時(shí),position 被移動(dòng),指向緩沖區(qū)中的下一個(gè)單元以插入數(shù)據(jù)。 position 最大值為 capacity -1
從緩沖區(qū)讀取數(shù)據(jù)時(shí),也可以從給定位置開始讀取數(shù)據(jù)。 當(dāng)緩沖區(qū)從寫入模式切換到讀取模式時(shí),position 將重置為 0 。當(dāng)從緩沖區(qū)讀取數(shù)據(jù)時(shí),將從 position 位置開始讀取數(shù)據(jù),讀取后會(huì)將 position 移動(dòng)到下一個(gè)要讀取的位置。
limit
在寫入模式下,Buffer 的 limit 是可以寫入緩沖區(qū)的數(shù)據(jù)量的限制,此時(shí) limit=capacity。
將緩沖區(qū)切換為讀取模式時(shí),limit 表示最多能讀到多少數(shù)據(jù)。 因此,當(dāng)將 Buffer 切換到讀取模式時(shí),limit被設(shè)置為之前寫入模式的寫入位置(position ),換句話說(shuō),你能讀到之前寫入的所有數(shù)據(jù)(例如之前寫寫入了 6 個(gè)字節(jié),此時(shí) position=6 ,然后切換到讀取模式,limit 代表最多能讀取的字節(jié)數(shù),因此 limit 也等于 6)。
分配緩沖區(qū)
要獲取 Buffer 對(duì)象,必須先分配它。 每個(gè) Buffer 類都有一個(gè) allocate() 方法來(lái)執(zhí)行此操作。 下面是一個(gè)顯示ByteBuffer分配的示例,容量為48字節(jié):
ByteBuffer buffer = ByteBuffer.allocate(48); //創(chuàng)建容量為48字節(jié)的緩沖區(qū)
將數(shù)據(jù)寫入緩沖區(qū)
可以通過(guò)兩種方式將數(shù)據(jù)寫入 Buffer:
- 將數(shù)據(jù)從通道寫入緩沖區(qū)
- 通過(guò)緩沖區(qū)的 put() 方法,自己將數(shù)據(jù)寫入緩沖區(qū)。
這是一個(gè)示例,顯示了 Channel 如何將數(shù)據(jù)寫入 Buffer:
int data = fileChannel.read(buffer); // 將 Channel 的數(shù)據(jù)讀入緩沖區(qū),返回讀入到緩沖區(qū)的字節(jié)數(shù)
buffer.put(127); // 此處的 127 是 byte 類型
put() 方法有許多其他版本,允許以多種不同方式將數(shù)據(jù)寫入 Buffer 。 例如,在特定位置寫入,或?qū)⒁粋€(gè)字節(jié)數(shù)組寫入緩沖區(qū)。
flip() 切換緩沖區(qū)的讀寫模式
flip() 方法將 Buffer 從寫入模式切換到讀取模式。 調(diào)用 flip() 會(huì)將 position 設(shè)置回 0,并將 limit 的值設(shè)置為切換之前的 position 值。換句話說(shuō),limit 表示之前寫進(jìn)了多少個(gè) byte、char 等 —— 現(xiàn)在能讀取多少個(gè) byte、char 等。
從緩沖區(qū)讀取數(shù)據(jù)
有兩種方法可以從 Buffer 中讀取數(shù)據(jù):
- 將數(shù)據(jù)從緩沖區(qū)讀入通道。
- 使用 get() 方法之一,自己從緩沖區(qū)讀取數(shù)據(jù)。
以下是將緩沖區(qū)中的數(shù)據(jù)讀入通道的示例:
int bytesWritten = fileChannel.write(buffer);
byte aByte = buffer.get();
和 put() 方法一樣,get() 方法也有許多其他版本,允許以多種不同方式從 Buffer 中讀取數(shù)據(jù)。有關(guān)更多詳細(xì)信息,請(qǐng)參閱JavaDoc以獲取具體的緩沖區(qū)實(shí)現(xiàn)。
以下列出 ByteBuffer 類的部分方法:
| 方法 | 描述 |
|---|---|
| byte[] array() | 返回實(shí)現(xiàn)此緩沖區(qū)的 byte 數(shù)組,此緩沖區(qū)的內(nèi)容修改將導(dǎo)致返回的數(shù)組內(nèi)容修改,反之亦然。 |
| CharBuffer asCharBuffer() | 創(chuàng)建此字節(jié)緩沖區(qū)作為新的獨(dú)立的char 緩沖區(qū)。新緩沖區(qū)的內(nèi)容將從此緩沖區(qū)的當(dāng)前位置開始 |
| XxxBuffer asXxxBuffer() | 同上,創(chuàng)建對(duì)應(yīng)的 Xxx 緩沖區(qū),Xxx 可為 Short/Int/Long/Float/Double |
| byte get() | 相對(duì) get 方法。讀取此緩沖區(qū)當(dāng)前位置的字節(jié),然后該 position 遞增。 |
| ByteBuffer get(byte[] dst, int offset, int length) | 相對(duì)批量 get 方法,后2個(gè)參數(shù)可省略 |
| byte get(int index) | 絕對(duì) get 方法。讀取指定索引處的字節(jié)。 |
| char getChar() | 用于讀取 char 值的相對(duì) get 方法。 |
| char getChar(int index) | 用于讀取 char 值的絕對(duì) get 方法。 |
| xxx getXxx(int index) | 用于讀取 xxx 值的絕對(duì) get 方法。index 可以選,指定位置。 |
| 眾多 put() 方法 | 參考以上 get() 方法 |
| static ByteBuffer wrap(byte[] array) | 將 byte 數(shù)組包裝到緩沖區(qū)中。 |
rewind() 倒帶
Buffer對(duì)象的 rewind() 方法將 position 設(shè)置回 0,因此可以重讀緩沖區(qū)中的所有數(shù)據(jù), limit 則保持不變。
clear() 和 compact()
如果調(diào)用 clear() ,則將 position 設(shè)置回 0 ,并將 limit 被設(shè)置成 capacity 的值。換句話說(shuō),Buffer 被清空了。 但是 Buffer 中的實(shí)際存放的數(shù)據(jù)并未清除。
如果在調(diào)用 clear() 時(shí)緩沖區(qū)中有任何未讀數(shù)據(jù),數(shù)據(jù)將被“遺忘”,這意味著不再有任何標(biāo)記告訴讀取了哪些數(shù)據(jù),還沒(méi)有讀取哪些數(shù)據(jù)。
如果緩沖區(qū)中仍有未讀數(shù)據(jù),并且想稍后讀取它,但需要先寫入一些數(shù)據(jù),這時(shí)候應(yīng)該調(diào)用 compact() ,它會(huì)將所有未讀數(shù)據(jù)復(fù)制到 Buffer 的開頭,然后它將 position 設(shè)置在最后一個(gè)未讀元素之后。 limit 屬性仍設(shè)置為 capacity ,就像 clear() 一樣。 現(xiàn)在緩沖區(qū)已準(zhǔn)備好寫入,并且不會(huì)覆蓋未讀數(shù)據(jù)。
mark() 和 reset()
以通過(guò)調(diào)用 Buffer 對(duì)象的 mark() 方法在 Buffer 中標(biāo)記給定位置。 然后,可以通過(guò)調(diào)用 Buffer.reset() 方法將位置重置回標(biāo)記位置,就像在標(biāo)準(zhǔn) IO 中一樣。
buffer.mark();
// 調(diào)用 buffer.get() 等方法讀取數(shù)據(jù)...
buffer.reset(); // 設(shè)置 position 回到 mark 位置。
equals() 和 compareTo()
可以使用 equals() 和 compareTo() 比較兩個(gè)緩沖區(qū)。
equals() 成立的條件:
- 它們的類型相同(byte,char,int等)
- 它們?cè)诰彌_區(qū)中具有相同數(shù)量的剩余字節(jié),字符等。
- 所有剩余的字節(jié),字符等都相等。
如上,equals 僅比較緩沖區(qū)的一部分,而不是它內(nèi)部的每個(gè)元素。 實(shí)際上,它只是比較緩沖區(qū)中的其余元素。
compareTo() 方法比較兩個(gè)緩沖區(qū)的剩余元素(字節(jié),字符等), 在下列情況下,一個(gè) Buffer 被視為“小于”另一個(gè) Buffer:
- 第一個(gè)不相等的元素小于另一個(gè) Buffer 中對(duì)應(yīng)的元素 。
- 所有元素都相等,但第一個(gè) Buffer 在第二個(gè) Buffer 之前耗盡了元素(第一個(gè) Buffer 元素較少)。