Java NIO 學(xué)習(xí)筆記(一)----概述,Channel/Buffer

目錄:
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 由以下核心組件組成:

  1. 通道和緩沖區(qū)
    在標(biāo)準(zhǔn) IO API 中,使用字節(jié)流和字符流。 在 NIO 中使用通道和緩沖區(qū)。 數(shù)據(jù)總是從通道讀入緩沖區(qū),或從緩沖區(qū)寫入通道。

  2. 非阻塞IO
    NIO 可以執(zhí)行非阻塞 IO 。 例如,當(dāng)通道將數(shù)據(jù)讀入緩沖區(qū)時(shí),線程可以執(zhí)行其他操作。 并且一旦數(shù)據(jù)被讀入緩沖區(qū),線程就可以繼續(xù)處理它。 將數(shù)據(jù)寫入通道也是如此。

  3. 選擇器
    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 :


通道將數(shù)據(jù)讀入緩沖區(qū),緩沖區(qū)將數(shù)據(jù)寫入通道

有幾種 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 的線程圖示:

1個(gè)線程使用選擇器處理3個(gè)通道

要使用選擇器,需要使用它注冊(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è)步驟:

  1. 將數(shù)據(jù)寫入緩沖區(qū)
  2. 調(diào)用 buffer.flip() 反轉(zhuǎn)讀寫模式
  3. 從緩沖區(qū)讀取數(shù)據(jù)
  4. 調(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ū)的工作原理。 這些是:

  1. capacity : 容量緩沖區(qū)的容量,是它所包含的元素的數(shù)量。不能為負(fù)并且不能更改。
  2. position :緩沖區(qū)的位置 是下一個(gè)要讀取或?qū)懭氲脑氐乃饕?。不能為?fù),并且不能大于 limit
  3. 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:

  1. 將數(shù)據(jù)從通道寫入緩沖區(qū)
  2. 通過(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ù):

  1. 將數(shù)據(jù)從緩沖區(qū)讀入通道。
  2. 使用 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() 成立的條件:

  1. 它們的類型相同(byte,char,int等)
  2. 它們?cè)诰彌_區(qū)中具有相同數(shù)量的剩余字節(jié),字符等。
  3. 所有剩余的字節(jié),字符等都相等。

如上,equals 僅比較緩沖區(qū)的一部分,而不是它內(nèi)部的每個(gè)元素。 實(shí)際上,它只是比較緩沖區(qū)中的其余元素。

compareTo() 方法比較兩個(gè)緩沖區(qū)的剩余元素(字節(jié),字符等), 在下列情況下,一個(gè) Buffer 被視為“小于”另一個(gè) Buffer:

  1. 第一個(gè)不相等的元素小于另一個(gè) Buffer 中對(duì)應(yīng)的元素 。
  2. 所有元素都相等,但第一個(gè) Buffer 在第二個(gè) Buffer 之前耗盡了元素(第一個(gè) Buffer 元素較少)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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