1. 【死磕 NIO】— 深入分析Buffer

        共 7245字,需瀏覽 15分鐘

         ·

        2021-11-16 02:26

        大家好,我是大明哥,今天我們來(lái)看看 Buffer。

        上面幾篇文章詳細(xì)介紹了 IO 相關(guān)的一些基本概念,如阻塞、非阻塞、同步、異步的區(qū)別,Reactor 模式、Proactor 模式。以下是這幾篇文章的鏈接,有興趣的同學(xué)可以閱讀下:

        從這篇文章開始,我們將回歸 NIO 方面的相關(guān)知識(shí),首先從 NIO 的三大核心組件說(shuō)起。

        • Buffer

        • Channel

        • Selector

        首先是 Buffer

        Buffer

        Buffer 是一個(gè)抽象類,主要用作緩沖區(qū),其實(shí)質(zhì)我們可以認(rèn)為是一個(gè)可以寫入數(shù)據(jù),然后從中讀取數(shù)據(jù)的內(nèi)存塊。這塊內(nèi)存被包裝成 NIO Buffer 對(duì)象,并提供一系列的方法便于我們?cè)L問(wèn)這塊內(nèi)存。

        要理解 Buffer 的工作原理,首先就要理解它的 4 個(gè)索引:

        • capacity:容量

        • position:位置

        • limit:界限

        • mark:標(biāo)記

        capacity 則表示該 Buffer 的容量,而 position 和 limit 的含義取決于 Buffer 處于什么模式(讀模式或者寫模式),下圖描述讀寫模式下這三種屬性的含義

        • capacity

        capacity 表示容量,Buffer 是一個(gè)內(nèi)存塊,其存儲(chǔ)數(shù)據(jù)的最大大小就是 capacity。我們不斷地往 Buffer 中寫入數(shù)據(jù),當(dāng) Buffer 被寫滿后也就是存儲(chǔ)的數(shù)據(jù)達(dá)到 capacity 了就需要將其清空,才能繼續(xù)寫入數(shù)據(jù)。

        • position

        position 的含義取決于 Buffer 處于寫模式還是讀模式:

        • 如果是寫模式,則寫入的地方就是所謂的 position,其初始值是 0,最大值是 capacity - 1,當(dāng)往 Buffer 中寫入一個(gè)數(shù)據(jù)時(shí),position 就會(huì)向前移動(dòng)到下一個(gè)待寫入的位置。

        • 如果是讀模式,則讀取數(shù)據(jù)的地方就是 position。當(dāng)執(zhí)行 flip() 將 buffer 從寫模式切換到讀模式時(shí),position 會(huì)被重置為 0,隨著數(shù)據(jù)不斷的讀取,position 不斷地向前移,直到 limit。

        • limit

        與 position 一樣,limit 的含義也取決于 Buffer 處于何種模式:

        • 寫模式:當(dāng) Buffer 處于寫模式時(shí),limit 是指能夠往 Buffer 中寫入多少數(shù)據(jù),其值等于 capacity

        • 讀模式:當(dāng) Buffer 處于讀模式時(shí),limit 表示能夠從 Buffer 中最多能夠讀取多少數(shù)據(jù)出來(lái),所以當(dāng) Buffer 從寫模式切換到讀模式時(shí),limit 會(huì)被設(shè)置寫模式下的 position 的值

        • mark

        mark 僅僅只是一個(gè)標(biāo)識(shí),可以通過(guò) mark() 方法進(jìn)行設(shè)置,設(shè)置值為當(dāng)前的 position

        Buffer 方法

        Buffer 提供了一系列的方法用來(lái)操作它,比如 clear() 用來(lái)清空緩沖區(qū),filp() 用來(lái)讀切換等等方法,下面將依次演示 Buffer 的主要方法,包含從 Buffer 獲取實(shí)例、寫入數(shù)據(jù)、讀取數(shù)據(jù)、重置等等一個(gè)系列的操作流程,同時(shí)將 position、limit 兩個(gè)參數(shù)打印出來(lái),便于我們更好地理解 Buffer。

        allocate()

        要獲取一個(gè) Buffer 對(duì)象,首先就要為期分配內(nèi)存空間,使用 allocate() 方法分配內(nèi)存空間,如下:

        DoubleBuffer buffer = DoubleBuffer.allocate(10);

        System.out.println("================= allocate 10 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        這里分配了 10 * sikeof(double) 字節(jié)的內(nèi)存空間。需要注意的是 allocate() 里面參數(shù)并不是字節(jié)數(shù),而是寫入對(duì)象的數(shù)量,比如上面實(shí)例參數(shù)是 10 ,表明我們可以寫 10 個(gè) double 對(duì)象。

        結(jié)果如下:

        ================= allocate 10 后 =================
        capacity = 10
        position = 0
        limit = 10

        此時(shí),Buffer 的情況如下:

        put()

        調(diào)用 allocate() 分配內(nèi)存后,得到 DoubleBuffer 實(shí)例對(duì)象,該對(duì)象目前處于寫模式,我們可以通過(guò) put() 方法向 Buffer 里面寫入數(shù)據(jù)。

        buffer.put(1);
        buffer.put(2);

        System.out.println("================= put 1、2 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        調(diào)用 put() 往 DoubleBuffer 里面存放 2 個(gè)元素,此時(shí),各自參數(shù)值如下:

        ================= put 1、2 后 =================
        capacity = 10
        position = 2
        limit = 10

        我們看到 position 的值變成了 2 ,指向第三個(gè)可以寫入元素的位置。這個(gè)時(shí)候我們?cè)賹懭?3 個(gè)元素:

        buffer.put(3);
        buffer.put(4);
        buffer.put(5);

        System.out.println("================= put 3、4、5 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        得到結(jié)果如下:

        ================= put 3、4、5 后 =================
        capacity = 10
        position = 5
        limit = 10

        此時(shí),position 的值變成 5 ,指向第 6 個(gè)可以寫入元素的位置。

        該 Buffer 的情況如下:

        flip()

        調(diào)用 put() 方法向 Buffer 中存儲(chǔ)數(shù)據(jù)后,這時(shí) Buffer 仍然處于寫模式狀態(tài),在寫模式狀態(tài)下我們是不能直接從 Buffer 中讀取數(shù)據(jù)的,需要調(diào)用 flip() 方法將 Buffer 從寫模式切換為讀模式。

        buffer.flip();
        System.out.println("================= flip 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        得到的結(jié)果如下:

        ================= flip 后 =================
        capacity = 10
        position = 0
        limit = 5

        調(diào)用 flip() 方法將 Buffer 從寫模式切換為讀模式后,Buffer 的參數(shù)發(fā)生了微秒的變化:position = 0,limit = 5。前面說(shuō)過(guò)在讀模式下,limit 代表是 Buffer 的可讀長(zhǎng)度,它等于寫模式下的 position,而 position 則是讀的位置。

        flip() 方法主要是將 Buffer 從寫模式切換為讀模式,其調(diào)整的規(guī)則如下:

        • 設(shè)置可讀的長(zhǎng)度 limit。將寫模式寫的 Buffer 中內(nèi)容的最后位置 position 值變成讀模式下的 limit 位置值,新的 limit 值作為讀越界位置

        • 設(shè)置讀的起始位置。將 position 的值設(shè)置為 0 ,表示從 0 位置處開始讀

        • 如果之前有 mark 保存的標(biāo)記位置,也需要消除,因?yàn)槟鞘菍懩J较碌?mark 標(biāo)記

        調(diào)動(dòng) flip() 后,該 Buffer 情況如下:

        get()

        調(diào)用 flip() 將 Buffer 切換為讀模式后,就可以調(diào)用 get() 方法讀取 Buffer 中的數(shù)據(jù)了,get() 讀取數(shù)據(jù)很簡(jiǎn)單,每次從 position 的位置讀取一個(gè)數(shù)據(jù),并且將 position 向前移動(dòng) 1 位。如下:

        System.out.println("讀取第 1 個(gè)位置的數(shù)據(jù):" + buffer.get());
        System.out.println("讀取第 2 個(gè)位置的數(shù)據(jù):" + buffer.get());
        System.out.println("================= get 2 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        連續(xù)調(diào)用 2 次 get() 方法,輸出結(jié)果:

        讀取第 1 個(gè)位置的數(shù)據(jù):1.0
        讀取第 2 個(gè)位置的數(shù)據(jù):2.0
        ================= get 2 后 =================
        capacity = 10
        position = 2
        limit = 5

        position 的值變成了 2 ,表明它向前移動(dòng)了 2 位,此時(shí),Buffer 如下:

        我們知道 limit 表明當(dāng)前 Buffer 最大可讀位置,buffer 也是一邊讀,position 位置一邊往前移動(dòng),那如果越界讀取呢?

        System.out.println("讀取第 3 個(gè)位置的數(shù)據(jù):" + buffer.get());
        System.out.println("讀取第 4 個(gè)位置的數(shù)據(jù):" + buffer.get());
        System.out.println("讀取第 5 個(gè)位置的數(shù)據(jù):" + buffer.get());
        System.out.println("讀取第 6 個(gè)位置的數(shù)據(jù):" + buffer.get());
        System.out.println("讀取第 7 個(gè)位置的數(shù)據(jù):" + buffer.get());

        limit = 5,6 、7 位置明顯越界了,如果越界讀取,Buffer 會(huì)拋出 BufferUnderflowException,如下:

        讀取第 3 個(gè)位置的數(shù)據(jù):3.0
        讀取第 4 個(gè)位置的數(shù)據(jù):4.0
        讀取第 5 個(gè)位置的數(shù)據(jù):5.0
        Exception in thread "main" java.nio.BufferUnderflowException
        at java.nio.Buffer.nextGetIndex(Buffer.java:500)
        at java.nio.HeapDoubleBuffer.get(HeapDoubleBuffer.java:135)
        at com.chenssy.study.nio.BufferTest.main(BufferTest.java:48)

        rewind()

        position 是隨著讀取的進(jìn)度一直往前移動(dòng)的,那如果我想在讀取一遍數(shù)據(jù)呢?使用 rewind() 方法,可以進(jìn)行重復(fù)讀。rewind() 也叫做倒帶,就想播放磁帶一樣,倒回去重新讀。

        buffer.rewind();
        System.out.println("================= rewind 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        運(yùn)行結(jié)果:

        ================= rewind 后 =================
        capacity = 10
        position = 0
        limit = 5

        可以看到,僅僅只是將 position 的值設(shè)置為了 0,limit 的值保持不變。

        clear() 和 compact()

        flip() 方法用于將 Buffer 從寫模式切換到讀模式,那怎么將 Buffer 從讀模式切換至寫模式呢?可以調(diào)用 clear()compact() 兩個(gè)方法。

        • clear()
        buffer.clear();

        System.out.println("================= clear 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        運(yùn)行結(jié)果如下:

        ================= clear 后 =================
        capacity = 10
        position = 0
        limit = 10

        調(diào)用 clear() 后,我們發(fā)現(xiàn) position 的值變成了 0,limit 值變成了 10,也就是 Buffer 被清空了,回歸到最初始狀態(tài)。但是里面的數(shù)據(jù)仍然是存在的,只是沒(méi)有標(biāo)記哪些數(shù)據(jù)是已讀,哪些為未讀。

        • compact()

        compact() 方法也可以將 Buffer 從讀模式切換到寫模式,它跟 clear() 有一些區(qū)別。

        buffer.compact();

        System.out.println("================= compact 后 =================");
        System.out.println("capacity = " + buffer.capacity());
        System.out.println("position = " + buffer.position());
        System.out.println("limit = " + buffer.limit());

        運(yùn)行結(jié)果如下:

        ================= compact 后 =================
        capacity = 10
        position = 3
        limit = 10

        可以看到 position 的值為 3,它與 clear() 區(qū)別就在于,它會(huì)將所有未讀的數(shù)據(jù)全部復(fù)制到 Buffer 的前面(5次put(),兩次 get()),將 position 設(shè)置到這些數(shù)據(jù)后面,所以此時(shí)是從未讀的數(shù)據(jù)后面開始寫入新的數(shù)據(jù),Buffer 情況如下:

        mark() 和 reset()

        調(diào)用 mark() 方法可以標(biāo)志一個(gè)指定的位置(即設(shè)置 mark 的值),之后調(diào)用 reset() 時(shí),position 又會(huì)回到之前標(biāo)記的位置。

        通過(guò)上面的步驟演示,我想小伙伴基本上已經(jīng)掌握了 Buffer 的使用方法,這里簡(jiǎn)要總結(jié)下,使用 Buffer 的步驟如下:

        1. 將數(shù)據(jù)寫入 Buffer 中

        2. 調(diào)用 flip() 方法,將 Buffer 切換為讀模式

        3. 從 Buffer 中讀取數(shù)據(jù)

        4. 調(diào)用 clear() 或者 compact() 方法將 Buffer 切換為寫模式

        Buffer 的類型

        在 NIO 中主要有 8 中 Buffer,分別如下:

        • ByteBuffer

        • CharBuffer

        • DoubleBuffer

        • FloatBuffer

        • IntBuffer

        • LongBuffer

        • ShortBuffer

        • MappedByteBuffer

        其 UML 類圖如下:

        這些不同的 Buffer 類型代表了不同的數(shù)據(jù)類型,使得可以通過(guò) Buffer 直接操作如 char、short 等類型的數(shù)據(jù)而不是字節(jié)數(shù)據(jù)。這些 Buffer 基本上覆蓋了所有能從 IO 中傳輸?shù)?Java 基本數(shù)據(jù)類型,其中 MappedByteBuffer 是專門用于內(nèi)存映射的的一種 ByteBuffer,后續(xù)會(huì)專門介紹。

        到這里 Buffer 也就介紹完畢了,下篇文章將介紹它的協(xié)作者 Channel。


        瀏覽 74
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 欧美日韩一二三四 | 青青青草视频在线观看 | 法国男男做爰gay | аⅴ资源新版在线天堂 | 69av在线观看视频 |