Java NIO Buffer(netty源碼死磕1.2)

【基礎篇】netty源碼死磕1.2:  html

NIO Bufferjava


1. Java NIO Buffer

Buffer是一個抽象類,位於java.nio包中,主要用做緩衝區。Buffer緩衝區本質上是一塊能夠寫入數據,而後能夠從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,並提供了一組方法,用來方便的訪問該塊內存。編程

注意:Buffer是非線程安全類。緩存

1.1. Buffer類型的標記屬性

Buffer在內部也是利用byte[]做爲內存緩衝區,只不過多提供了一些標記變量屬性而已。當多線程訪問的時候,能夠清楚的知道當前數據的位置。安全

有三個重要的標記屬性:capacity、position、limit。多線程

除此以外,還有一個標記屬性:mark,能夠臨時保持一個特定的position,須要的時候,能夠恢復到這個位置。app

1.1.1. capacity

做爲一個內存塊,Buffer有一個固定的大小值,也叫「capacity」。你只能往裏寫capacity個數據。一旦Buffer滿了,就不能再寫入。post

capacity與緩存的數據類型相關。指的不是內存的字節的數量,而是寫入的對象的數量。好比使用的是一個保存double類型的Buffer(DoubleBuffer),寫入的數據是double類型, 若是其 capacity 是100,那麼咱們最多能夠寫入100個 double 數據.學習

capacity一旦初始化,就不能不會改變。大數據

緣由是什麼呢?

Buffer對象在初始化時,會按照capacity分配內部的內存。內存分配好後,大小就不能變了。分配內存時,通常使用Buffer的抽象子類ByteBuffer.allocate()方法,其實是生成ByteArrayBuffer類。

1.1.2. position

position表示當前的位置。position在Buffer的兩種模式下的值是不一樣的。

讀模式下的position的值爲:

當讀取數據時,也是從position位置開始讀。當將Buffer從寫模式切換到讀模式,position會被重置爲0。當從Buffer的position處讀取數據時,position向前移動到下一個可讀的位置。

寫模式下的position的值爲:

在寫模式下,當寫數據到Buffer中時,position表示當前的寫入位置。初始的position值爲0,position最大可爲capacity – 1。

每當一個數據(byte、long等)寫到Buffer後, position會向後移動到下一個可插入數據的可寫的位置。

1.1.3. limit

limit表示最大的限制。在Buffer的兩種模式下,limit的值是不一樣的。

讀模式下的limit的值爲:

讀模式下,Buffer的limit表示最多能從Buffer裏讀多少數據。當Buffer從寫切換到讀模式時,limit的值,設置成寫模式的position 值,也是是寫模式下以前寫入的數量值。

舉一個簡單的例子,說明一下讀模式下的limit值:

先向Buffer寫數據,Buffer在寫模式。每寫入一個數據,position向後面移動一個位置,值加一。假定寫入了5個數,當寫入完成後,position的值爲5。這時,就能夠讀取數據了。當開始讀取數據時,Buffer切換到讀模式。limit的值,先會被設置成寫入數據時的position值。這裏是5,表示能夠讀取的最大限制是5個數。

寫模式下的limit的值爲:

limit表示表示能夠寫入的數據最大限制。在切換成寫模式時,limit的值會被更改,設置成Buffer的capacity,爲Buffer的容量。

1.1.4. 總結:

在Buffer的四個屬性之間,有一個簡單的數量關係,以下:

capacity>=limit>=position>=mark>=0

用一個表格,對着4個屬性的進行一下對比:

屬性

描述

capacity

容量,便可以容納的最大數據量;在緩衝區建立時被設定而且不能改變

limit

上界,緩衝區中當前數據量

position

位置,下一個要被讀或寫的元素的索引

mark(位置標記)

調用mark(pos)來設置mark=pos,再調用reset()可讓position恢復到標記的位置即position=mark

1.2. Buffer 類型

在NIO中主要有八種緩衝區類,分別以下:

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

MappedByteBuffer

wps6DEB.tmp



這些 Buffer 覆蓋了能從 IO 中傳輸的全部的 Java 基本數據類型。其中MappedByteBuffer是專門用於內存映射的一種ByteBuffer)。

1.3. Buffer中的方法

本節結合Buffer的幾個方法,作了一個完整的實例,包含了從Buffer實例的獲取、寫入、讀取、重複讀、標記和重置等一個系列操做的完整流程。

1.3.1. 獲取allocate()方法

爲了獲取一個 Buffer 對象,咱們首先須要分配內存空間。分配內存空間使用allocate()方法。

public static void allocatTest()
{
byteBuffer = IntBuffer.allocate(20);
Logger.info("------------after allocate------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

byteBuffer = IntBuffer.allocate(20);

這裏咱們分配了20* sizeof(int)字節的內存空間.

輸出的結果以下:

       main |>  分配內存

         allocatTest |>  ------------after allocate------------------

         allocatTest |>  position=0

         allocatTest |>  limit=20

         allocatTest |>  capacity=20

經過結果,能夠看到Buffer屬性的值。

1.3.2. 寫put()方法

調用allocate分配內存後,buffer處於寫模式。能夠經過buffer的put方法寫入數據。put方法有一個要求,須要寫入的數據類型與Buffer的類型一致。

接着前面的例子,繼續上寫入的實例代碼:

public static void putTest()
{
for (int i = 0; i < 5; i++)
    {
byteBuffer.put(i);
}
    Logger.info("------------after put------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

寫入5個元素後,輸出的結果爲:

  main |>  寫入

             putTest |>  ------------after putTest------------------

             putTest |>  position=5

             putTest |>  limit=20

             putTest |>  capacity=20

調用了put方法後,buffer處於寫模式。寫入5個數據後,能夠看到,position 變成了5,指向了第6個能夠寫入的元素位置。

除了在新建的buffer以後,如何將buffer切換成寫模式呢?

調用 Buffer.clear() 清空或 Buffer.compact()壓縮方法,能夠將 Buffer 轉換爲寫模式。

1.3.3. 讀切換flip()方法

put方法寫入數據以後,能夠直接從buffer中讀嗎?

呵呵,不能。

還須要調用filp()走一個轉換的工做。flip()方法是Buffer的一個模式轉變的重要方法。簡單的說,是寫模式翻轉成讀模式——寫轉讀。

接着前面的例子,繼續上flip()方法的例子代碼:

public static void flipTest()
{
byteBuffer.flip();
Logger.info("------------after flip ------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

接着上一步的寫入,在調用flip以後,buffer的屬性有一些奇妙的變化。

運行上面的程序,輸出以下:

 main |>  翻轉

            flipTest |>  ------------after flipTest ------------------

            flipTest |>  position=0

            flipTest |>  limit=5

            flipTest |>  capacity=20

注意到沒有,position從前一個小節的5,變成了0。而limit的保存了以前的position,從20變成5。

這是爲何呢? 先看其源碼,Buffer.flip()方法的源碼以下:

public final Buffer flip() {

    limit = position;

    position = 0;

    mark = UNSET_MARK;

    return this;

}

解釋一下啊,flip()方法主要是從讀模式切換成寫模式,調整的規則是:

(1)首先設置可讀的長度limit。將寫模式下的Buffer中內容的最後位置position值變爲讀模式下的limit位置值,新的limit值做爲讀越界位置;

(2)其次設置讀的起始位置。將當position值置爲0,表示從0位置開始讀。轉換後重頭開始讀。

(3)若是以前有mark保存的標記位置,還要消除。由於那是寫模式下的mark標記。

1.3.4. 讀get() 方法

get()讀數據很簡單,每次從postion的位置讀取一個數據,而且進行相應的buffer屬性的調整。

接着前面的例子,繼續上讀取buffer的例子代碼:

public static void getTest()
{
    Logger.info("------------after &getTest 2------------------");
    for (int i = 0; i < 2; i++)
    {
int j = byteBuffer.get();
Logger.info("j = " + j);
}
    Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
Logger.info("------------after &getTest 3------------------");
    for (int i = 0; i < 3; i++)
    {
int j = byteBuffer.get();
Logger.info("j = " + j);
}
    Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

先讀2個,再讀3個,輸出的Buffer屬性值以下:

 main |>  讀取 

             getTest |>  ------------after &getTest 2------------------ 

             getTest |>  j = 0 

             getTest |>  j = 1 

             getTest |>  position=2 

             getTest |>  limit=5 

             getTest |>  capacity=20 

             getTest |>  ------------after &getTest 3------------------ 

             getTest |>  j = 2 

             getTest |>  j = 3 

             getTest |>  j = 4 

             getTest |>  position=5 

             getTest |>  limit=5 

             getTest |>  capacity=20 

讀完以後,緩存的position 值變成了一個沒有數據的元素位置,和limit的值相等,已經不能在讀了。

讀完以後,是否能夠直接寫數據呢?

不能。一旦讀取了全部的 Buffer 數據,那麼咱們必須清理 Buffer,讓其從新可寫,能夠調用 Buffer.clear() 或 Buffer.compact()。

1.3.5. 倒帶rewind()方法

已經讀完的數據,須要再讀一遍,能夠直接使用get方法嗎?

答案是,不能。怎麼辦呢?

使用rewind() 方法,能夠進重複讀的設置。rewind()也叫倒帶,就像播放磁帶同樣,倒回去,從新播放。

接着前面的例子,繼續上重複讀的例子代碼:

public static void rewindTest()
{
byteBuffer.rewind();
Logger.info("------------after flipTest ------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

實例的結果以下:

 main |>  重複讀

          rewindTest |>  ------------after flipTest ------------------

          rewindTest |>  position=0

          rewindTest |>  limit=5

          rewindTest |>  capacity=20

flip()方法主要是調整Buffer的 position 屬性,調整的規則是:

(1)position設回0,因此你能夠重讀Buffer中的全部數據;

(2)limit保持不變,數據量仍是同樣的,仍然表示能從Buffer中讀取多少個元素。

Buffer.rewind()方法的源碼以下:

public final Buffer rewind() {

position = 0;

mark = -1;

return this;

}

看到了實現的源碼應該就會清楚flip()的做用了。rewind()方法與flip()很類似,區別在於rewind()不會影響limit,而flip()會重設limit屬性值。

1.3.6. mark( )和reset( )

Buffer.mark()方法將當前的 position 的值保存起來,放在mark屬性中,讓mark屬性記住當前位置,以後能夠調用Buffer.reset()方法將 position 的值恢復回來。

Buffer.mark()和Buffer.reset()方法是一一配套使用的。都是須要操做mark屬性。

在重複讀的實例代碼中,讀到第3個元素,使用mark()方法,設置一下mark 屬性,保存爲第3個元素的位置。

下面上實例,演示一下mark和reset的結合使用。

實例繼續接着上面的rewind倒帶後的buffer 狀態,開始reRead重複讀,實例代碼以下:

public static void reRead()
{
    Logger.info("------------after reRead------------------");
    for (int i = 0; i < 5; i++)
    {
int j = byteBuffer.get();
Logger.info("j = " + j);
        if (i == 2)
        {
byteBuffer.mark();
}
    }
    Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

而後接着上一段reset()實例代碼,以下:

public static void afterReset()
{
    Logger.info("------------after reset------------------");
byteBuffer.reset();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

上面咱們調用 mark() 方法將當前的 position 保存起來(在讀模式,所以保存的是讀的 position)。接着使用 reset() 恢復原來的讀 position,所以讀 position 就爲3,能夠再次開始從第2個元素讀取數據.

輸出的結果是:

 afterReset |>  ------------after reset------------------

          afterReset |>  position=3

          afterReset |>  limit=5

          afterReset |>  capacity=20

調用reset以後,position的值爲3,表示能夠從第三個元素開始讀。

Buffer.mark()和Buffer.reset()其實很簡答,其源碼以下:

public final Buffer mark() {

mark = position;

return this;

}

public final Buffer reset() {

int m = mark;

if (m < 0)

throw new InvalidMarkException();

position = m;

return this;

}
1.3.7. clear()清空

clear()方法的做用有兩種:

(1)寫模式下,當一個 buffer 已經寫滿數據時,調用 clear()方法,切換成讀模式,能夠從頭讀取 buffer 的數據;

(2)讀模式下,調用 clear()方法,將buffer切換爲寫模式,將postion爲清零,limit設置爲capacity最大容量值,能夠一直寫入,直到buffer寫滿。

接着上面的實例,使用實例代碼,演示一下clear方法。

代碼以下:

public static void clearDemo()
{
    Logger.info("------------after clear------------------");
byteBuffer.clear();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

運行以後,結果以下:

main |>  清空

           clearDemo |>  ------------after clear------------------

           clearDemo |>  position=0

           clearDemo |>  limit=20

           clearDemo |>  capacity=20

在clear()以前,buffer是在讀模式下。clear()以後,能夠看到,清空了position 的值,設置爲起始位置。

clear 方法源碼:

public final Buffer clear() {

    position = 0;

    limit = capacity;

    mark = -1;

    return this;

}

根據源碼咱們能夠知道,clear 將 positin 設置爲0,將 limit 設置爲 capacity。

1.4. Buffer 的使用

1.4.1. 使用的基本步驟

總結一下,使用 NIO Buffer 的步驟以下:

一:將數據寫入到 Buffer 中;

二:調用 Buffer.flip()方法,將 NIO Buffer 轉換爲讀模式;

三:從 Buffer 中讀取數據;

四:調用 Buffer.clear() 或 Buffer.compact()方法,將 Buffer 轉換爲寫模式。

當咱們將數據寫入到 Buffer 中時,Buffer 會記錄咱們已經寫了多少的數據;當咱們須要從 Buffer 中讀取數據時,必須調用 Buffer.flip()將 Buffer 切換爲讀模式。

1.4.2. 完整的實例代碼
package com.crazymakercircle.iodemo.base;

import com.crazymakercircle.util.Logger;

import java.nio.IntBuffer;

public class BufferDemo
{
static IntBuffer byteBuffer = null;

public static void allocatTest()
{
byteBuffer = IntBuffer.allocate(20);

Logger.info("------------after allocate------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

public static void putTest()
{
for (int i = 0; i < 5; i++)
{
byteBuffer.put(i);

}

Logger.info("------------after putTest------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());

}

public static void flipTest()
{

byteBuffer.flip();
Logger.info("------------after flipTest ------------------");

Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

public static void rewindTest()
{

byteBuffer.rewind();
Logger.info("------------after flipTest ------------------");

Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}

public static void getTest()
{


Logger.info("------------after &getTest 2------------------");
for (int i = 0; i < 2; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
}


Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());

Logger.info("------------after &getTest 3------------------");

for (int i = 0; i < 3; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
}

Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());

}

public static void reRead()
{


Logger.info("------------after reRead------------------");
for (int i = 0; i < 5; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);

if (i == 2)
{
byteBuffer.mark();
}
}


Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());

}

public static void afterReset()
{


Logger.info("------------after reset------------------");

byteBuffer.reset();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());

}

public static void clearDemo()
{


Logger.info("------------after clear------------------");

byteBuffer.clear();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());

}

public static void main(String[] args)
{
Logger.info("分配內存");

allocatTest();

Logger.info("寫入");
putTest();

Logger.info("翻轉");

flipTest();

Logger.info("讀取");
getTest();

Logger.info("重複讀");
rewindTest();
reRead();

Logger.info("make&reset寫讀");

afterReset();
Logger.info("清空");

clearDemo();


}
}




源碼:


代碼工程:  JavaNioDemo.zip

下載地址:在瘋狂創客圈QQ羣文件共享。



無編程不創客,無案例不學習。瘋狂創客圈,一大波高手正在交流、學習中!

瘋狂創客圈 Netty 死磕系列 10多篇深度文章博客園 總入口】  QQ羣:104131248

相關文章
相關標籤/搜索