BIO到NIO源碼的一些事兒之NIO 下 Buffer解讀 上

前言

此係列文章會詳細解讀NIO的功能逐步豐滿的路程,爲Reactor-Netty 庫的講解鋪平道路。java

關於Java編程方法論-Reactor與Webflux的視頻分享,已經完成了Rxjava 與 Reactor,b站地址以下:編程

Rxjava源碼解讀與分享:www.bilibili.com/video/av345…api

Reactor源碼解讀與分享:www.bilibili.com/video/av353…數組

本系列源碼解讀基於JDK11 api細節可能與其餘版本有所差異,請自行解決jdk版本問題。安全

本系列前幾篇:dom

BIO到NIO源碼的一些事兒之BIOjvm

BIO到NIO源碼的一些事兒之NIO 上ide

BIO到NIO源碼的一些事兒之NIO 中函數

BIO到NIO源碼的一些事兒之NIO 下 之 Selector源碼分析

Buffer

在Java BIO中,經過BIO到NIO源碼的一些事兒之BIO開篇的Demo可知,全部的讀寫API,都是直接使用byte數組做爲緩衝區的,簡單直接。咱們來拿一個杯子作例子,咱們不講它的材質,只說它的使用屬性,一個杯子在使用過程當中會首先看其最大容量,而後加水,這裏給一個限制,即加到杯子中的水量爲杯子最大容量的一半,而後喝水,咱們最多也只能喝杯子裏所盛水量。由這個例子,咱們思考下,杯子是否是能夠看做是一個緩衝區,對於杯子倒水的節奏咱們是否是能夠輕易的控制,從而帶來諸多方便,那是否是能夠將以前BIO中的緩衝區也加入一些特性,使之變的和咱們使用杯子同樣便捷。 因而,咱們給buffer添加幾個屬性,對比杯子的最大容量,咱們設計添加一個capacity屬性,對比加上的容量限制,咱們設計添加一個limit屬性,對於加水加到杯中的當前位置,咱們設計添加一個position屬性,有時候咱們還想在杯子上本身作個標記,好比喝茶,我本身的習慣就是喝到杯里剩三分之一水的時候再加水加到一半,針對這個狀況,設計添加一個mark屬性。由此,咱們來總結下這幾個屬性的關係,limit不可能比capacity大的,position又不會大於limitmark能夠理解爲一個標籤,其也不會大於position,也就是mark <= position <= limit <= capacity

結合以上概念,咱們來對buffer中這幾個屬性使用時的行爲進行下描述:

  • capacity

    也就是緩衝區的容量大小。咱們只能往裏面寫capacitybytelongchar等類型。一旦Buffer滿了,須要將其清空(經過讀數據或者清除數據)才能繼續寫數據往裏寫數據。

  • position

    (1)當咱們寫數據到Buffer中時,position表示當前的位置。初始的position值爲0.當一個bytelongchar等數據寫到Buffer後,position會向前移動到下一個可插入數據的Buffer位置。position最大可爲capacity – 1

    (2)當讀取數據時,也是從某個特定位置讀。當將Buffer從寫模式切換到讀模式,position會被重置爲0. 當從Bufferposition處讀取數據時,position向前移動到下一個可讀的位置。

  • limit

    (1)在寫模式下,Bufferlimit表示你最多能往Buffer裏寫多少數據。寫模式下,limit等於Buffercapacity

    (2)讀模式時,limit表示你最多能讀到多少數據。所以,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到以前寫入的全部數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position

  • mark

    相似於喝茶喝到剩餘三分之一誰加水同樣,當buffer調用它的reset方法時,當前的位置position會指向mark所在位置,一樣,這個也根據我的喜愛,有些人就喜歡將水喝完再添加的,因此mark不必定總會被設定,但當它被設定值以後,那設定的這個值不能爲負數,同時也不能大於position。還有一種狀況,就是我喝水喝不下了,在最後將水一口喝完,則對照的此處的話,即若是對mark設定了值(並不是初始值-1),則在將positionlimit調整爲小於mark的值的時候將mark丟棄掉。若是並未對mark從新設定值(即仍是初始值-1),那麼在調用reset方法會拋出InvalidMarkException異常。

可見,通過包裝的Buffer是Java NIO中對於緩衝區的抽象。在Java有8中基本類型:byte、short、int、long、float、double、char、boolean,除了boolean類型外,其餘的類型都有對應的Buffer具體實現,可見,Buffer是一個用於存儲特定基本數據類型的容器。再加上數據時有序存儲的,並且Buffer有大小限制,因此,Buffer能夠說是特定基本數據類型的線性存儲有限的序列。

接着,咱們經過下面這幅圖來展現下上面幾個屬性的關係,方便你們更好理解:

Buffer的基本用法

先來看一個Demo:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {

  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);
}
aFile.close();
複製代碼

咱們拋去前兩行,來總結下buffer的使用步驟:

  1. 經過相應類型Buffer的allocate的靜態方法來分配指定類型大小的緩衝數據區域(此處爲buf);
  2. 寫入數據到Buffer;
  3. 調用flip()方法:Buffer從寫模式切換到讀模式;
  4. 從buffer讀取數據;
  5. 調用clear()方法或則compact()方法。

Buffer分配

那咱們依據上面的步驟來一一看下其相應源碼實現,這裏咱們使用ByteBuffer來解讀。首先是Buffer分配。

//java.nio.ByteBuffer#allocate
public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw createCapacityException(capacity);
    return new HeapByteBuffer(capacity, capacity);
}
//java.nio.ByteBuffer#allocateDirect
public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}
複製代碼

ByteBuffer是一個抽象類,具體的實現有HeapByteBufferDirectByteBuffer。分別對應Java堆緩衝區與堆外內存緩衝區。Java堆緩衝區本質上就是byte數組(由以前分析的,咱們只是在字節數組上面加點屬性,輔以邏輯,實現一些更復雜的功能),因此實現會比較簡單。而堆外內存涉及到JNI代碼實現,較爲複雜,因此咱們先來分析HeapByteBuffer的相關操做,隨後再專門分析DirectByteBuffer

咱們來看HeapByteBuffer相關構造器源碼:

//java.nio.HeapByteBuffer#HeapByteBuffer(int, int)
HeapByteBuffer(int cap, int lim) {            

    super(-1, 0, lim, cap, new byte[cap], 0);
    /* hb = new byte[cap]; offset = 0; */
    this.address = ARRAY_BASE_OFFSET;
}
//java.nio.ByteBuffer#ByteBuffer(int, int, int, int, byte[], int)
ByteBuffer(int mark, int pos, int lim, int cap,   
                byte[] hb, int offset)
{
    super(mark, pos, lim, cap);
    this.hb = hb;
    this.offset = offset;
}
//java.nio.Buffer#Buffer
Buffer(int mark, int pos, int lim, int cap) {       
    if (cap < 0)
        throw createCapacityException(cap);
    this.capacity = cap;
    limit(lim);
    position(pos);
    if (mark >= 0) {
        if (mark > pos)
            throw new IllegalArgumentException("mark > position: ("
                                                + mark + " > " + pos + ")");
        this.mark = mark;
    }
}
複製代碼

由上,HeapByteBuffer經過初始化字節數組hd,在虛擬機堆上申請內存空間。 因在ByteBuffer中定義有hb這個字段,它是一個byte[]類型,爲了獲取這個字段相對於當前這個ByteBuffer對象所在內存地址,經過private static final long ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class)中這個UNSAFE操做來獲取這個數組第一個元素位置與該對象所在地址的相對長度,這個對象的地址表明你的頭所在的位置,將這個數組看做你的鼻子,而這裏返回的是你的鼻子距離頭位置的那個長度,即數組第一個位置距離這個對象開始地址所在位置,這個是在class字節碼加載到jvm裏的時候就已經肯定了。 若是ARRAY_INDEX_SCALE = UNSAFE.arrayIndexScale(byte[].class)爲返回非零值,則可使用該比例因子以及此基本偏移量(ARRAY_BASE_OFFSET)來造成新的偏移量,以訪問這個類的數組元素。知道這些,在ByteBufferslice duplicate之類的方法,就能理解其操做了,就是計算數組中每個元素所佔空間長度獲得ARRAY_INDEX_SCALE,而後當我肯定我從數組第5個位置做爲該數組的開始位置操做時,我就可使用this.address = ARRAY_BASE_OFFSET + off * ARRAY_INDEX_SCALE。 咱們再經過下面的源碼對上述內容對比消化下:

//java.nio.HeapByteBuffer
protected HeapByteBuffer(byte[] buf, int mark, int pos, int lim, int cap, int off) {
    super(mark, pos, lim, cap, buf, off);
    /* hb = buf; offset = off; */
    this.address = ARRAY_BASE_OFFSET + off * ARRAY_INDEX_SCALE;

}

public ByteBuffer slice() {
return new HeapByteBuffer(hb,
                            -1,
                            0,
                            this.remaining(),
                            this.remaining(),
                            this.position() + offset);
}


ByteBuffer slice(int pos, int lim) {
    assert (pos >= 0);
    assert (pos <= lim);
    int rem = lim - pos;
return new HeapByteBuffer(hb,
                            -1,
                            0,
                            rem,
                            rem,
                            pos + offset);
}


public ByteBuffer duplicate() {
return new HeapByteBuffer(hb,
                            this.markValue(),
                            this.position(),
                            this.limit(),
                            this.capacity(),
                            offset);
}
複製代碼

Buffer的讀寫

每一個buffer都是可讀的,但不是每一個buffer都是可寫的。這裏,當buffer有內容變更的時候,會首先調用bufferisReadOnly判斷此buffer是否只讀,只讀buffer是不容許更改其內容的,但markpositionlimit的值是可變的,這是咱們人爲給其額外的定義,方便咱們增長功能邏輯的。當在只讀buffer上調用修改時,則會拋出ReadOnlyBufferException異常。咱們來看bufferput方法:

//java.nio.ByteBuffer#put(java.nio.ByteBuffer)
public ByteBuffer put(ByteBuffer src) {
    if (src == this)
        throw createSameBufferException();
    if (isReadOnly())
        throw new ReadOnlyBufferException();
    int n = src.remaining();
    if (n > remaining())
        throw new BufferOverflowException();
    for (int i = 0; i < n; i++)
        put(src.get());
    return this;
}
//java.nio.Buffer#remaining
public final int remaining() {
    return limit - position;
}

複製代碼

上面remaining方法表示還剩多少數據未讀,上面的源碼講的是,若是src這個ByteBuffersrc.remaining()的數量大於要存放的目標Buffer的還剩的空間,直接拋溢出的異常。而後經過一個for循環,將src剩餘的數據,依次寫入目標Buffer中。接下來,咱們經過src.get()來探索下Buffer的讀操做。

//java.nio.HeapByteBuffer#get()
public byte get() {
    return hb[ix(nextGetIndex())];
}

public byte get(int i) {
    return hb[ix(checkIndex(i))];
}
//java.nio.HeapByteBuffer#ix
protected int ix(int i) {
    return i + offset;
}
//java.nio.Buffer#nextGetIndex()
final int nextGetIndex() {                          
    if (position >= limit)
        throw new BufferUnderflowException();
    return position++;
}
複製代碼

這裏,爲了依次讀取數組中的數據,這裏使用nextGetIndex()來獲取要讀位置,即先返回當前要獲取的位置值,而後position本身再加1。以此在前面ByteBuffer#put(java.nio.ByteBuffer)所示源碼中的for循環中依次對剩餘數據的讀取。上述get(int i)不過是從指定位置獲取數據,實現也比較簡單HeapByteBuffer#ix也只是肯定所要獲取此數組對象指定位置數據,其中的offset表示第一個可讀字節在該字節數組中的位置(就比如我喝茶杯底三分之一水是不喝的,每次都從三分之一水量開始位置計算喝了多少或者加入多少水)。 接下來看下單個字節存儲到指定字節數組的操做,與獲取字節數組單個位置數據相對應,代碼比較簡單:

//java.nio.HeapByteBuffer#put(byte)
public ByteBuffer put(byte x) {

    hb[ix(nextPutIndex())] = x;
    return this;
}

public ByteBuffer put(int i, byte x) {

    hb[ix(checkIndex(i))] = x;
    return this;
}
//java.nio.Buffer#nextPutIndex()
final int nextPutIndex() {                          // package-private
    if (position >= limit)
        throw new BufferOverflowException();
    return position++;
}
複製代碼

前面的都是單個字節的,下面來說下批量操做字節數組是如何進行的,因過程知識點重複,這裏只講get,先看源碼:

//java.nio.ByteBuffer#get(byte[])
public ByteBuffer get(byte[] dst) {
    return get(dst, 0, dst.length);
}
//java.nio.ByteBuffer#get(byte[], int, int)
public ByteBuffer get(byte[] dst, int offset, int length) {
    // 檢查參數是否越界
    checkBounds(offset, length, dst.length);
     // 檢查要獲取的長度是否大於Buffer中剩餘的數據長度
    if (length > remaining())
        throw new BufferUnderflowException();
    int end = offset + length;
    for (int i = offset; i < end; i++)
        dst[i] = get();
    return this;
}
//java.nio.Buffer#checkBounds
static void checkBounds(int off, int len, int size) { // package-private
    if ((off | len | (off + len) | (size - (off + len))) < 0)
        throw new IndexOutOfBoundsException();
}
複製代碼

經過這個方法將這個buffer中的字節數據讀到咱們給定的目標數組dst中,由checkBounds可知,當要寫入目標字節數組的可寫長度小於將要寫入數據的長度的時候,會產生邊界異常。當要獲取的長度是大於Buffer中剩餘的數據長度時拋出BufferUnderflowException異常,當驗證經過後,接着就從目標數組的offset位置開始,從buffer獲取並寫入offset + length長度的數據。 能夠看出,HeapByteBuffer是封裝了對byte數組的簡單操做。對緩衝區的寫入和讀取本質上是對數組的寫入和讀取。使用HeapByteBuffer的好處是咱們不用作各類參數校驗,也不須要另外維護數組當前讀寫位置的變量了。 同時咱們能夠看到,Buffer中對position的操做沒有使用鎖保護,因此Buffer不是線程安全的。若是咱們操做的這個buffer會有多個線程使用,則針對該buffer的訪問應經過適當的同步控制機制來進行保護。

ByteBuffer的模式

jdk自己是沒這個說法的,只是按照咱們本身的操做習慣,咱們將Buffer分爲兩種工做模式,一種是接收數據模式,一種是輸出數據模式。咱們能夠經過Buffer提供的flip等操做來切換Buffer的工做模式。

咱們來新建一個容量爲10的ByteBuffer:

ByteBuffer.allocate(10);
複製代碼

由前面所學的HeapByteBuffer的構造器中的相關代碼可知,這裏的position被設置爲0,並且 capacitylimit設置爲 10,mark設置爲-1,offset設定爲0。 可參考下圖展現:

HeapByteBuffer初始化

新建的Buffer處於接收數據的模式,能夠向Buffer放入數據,在放入一個對應基本類型的數據後(此處假如放入一個char類型數據),position加一,參考咱們上面所示源碼,若是position已經等於limit了還進行put操做,則會拋出BufferOverflowException異常。 咱們向所操做的buffer中put 5個char類型的數據進去:

buffer.put((byte)'a').put((byte)'b').put((byte)'c').put((byte)'d').put((byte)'e');
複製代碼

會獲得以下結果視圖:

由以前源碼分析可知,Buffer的讀寫的位置變量都是基於position來作的,其餘的變量都是圍繞着它進行輔助管理的,因此若是從Buffer中讀取數據,要將Buffer切換到輸出數據模式(也就是讀模式)。此時,咱們就可使用Buffer提供了flip方法。

//java.nio.Buffer#flip
public Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}
複製代碼

咱們知道,在put的時候,會進行java.nio.Buffer#nextPutIndex()的調用,裏面會進行position >= limit,因此,此時再進行寫操做的話,會從第0個位置開始進行覆蓋,並且只能寫到flip操做以後limit的位置。

//java.nio.Buffer#nextPutIndex()
final int nextPutIndex() {                          // package-private
    if (position >= limit)
        throw new BufferOverflowException();
    return position++;
}
複製代碼

在作完put操做後,position會自增一下,因此,flip操做示意圖以下:

也是由於position爲0了,因此咱們能夠很方便的從Buffer中第0個位置開始讀取數據,不須要別的附加操做。由以前解讀可知,每次讀取一個元素,position就會加一,若是position已經等於limit還進行讀取,則會拋出BufferUnderflowException異常。

咱們經過flip方法把Buffer從接收寫模式切換到輸出讀模式,若是要從輸出模式切換到接收模式,可使用compact或者clear方法,若是數據已經讀取完畢或者數據不要了,使用clear方法,若是隻想從緩衝區中釋放一部分數據,而不是所有(即釋放已讀數據,保留未讀數據),而後從新填充,使用compact方法。

對於clear方法,咱們先來看它的源碼:

//java.nio.Buffer#clear
public Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}
複製代碼

咱們能夠看到,它的clear方法內並無作清理工做,只是修改位置變量,重置爲初始化時的狀態,等待下一次將數據寫入緩衝數組。 接着,來看compact操做的源碼:

//java.nio.HeapByteBuffer#compact
public ByteBuffer compact() {
    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
    position(remaining());
    limit(capacity());
    discardMark();
    return this;
}
//java.nio.ByteBuffer#position
ByteBuffer position(int newPosition) {
    super.position(newPosition);
    return this;
}
//java.nio.Buffer#position(int)
public Buffer position(int newPosition) {
    if (newPosition > limit | newPosition < 0)
        throw createPositionException(newPosition);
    position = newPosition;
    if (mark > position) mark = -1;
    return this;
}
//java.nio.ByteBuffer#limit
ByteBuffer limit(int newLimit) {
    super.limit(newLimit);
    return this;
}
//java.nio.Buffer#limit(int)
public Buffer limit(int newLimit) {
    if (newLimit > capacity | newLimit < 0)
        throw createLimitException(newLimit);
    limit = newLimit;
    if (position > limit) position = limit;
    if (mark > limit) mark = -1;
    return this;
}
//java.nio.Buffer#discardMark
final void discardMark() {                          
        mark = -1;
    }
複製代碼

這裏使用了數組的拷貝操做,將未讀元素轉移到該字節數組從0開始的位置,因爲remaining()返回的是limit - position,假如在flip操做的時候填入的元素有5個,那麼limit5,此時讀到了第三個元素,也就是在調用compactposition的數值爲2,那remaining()的值就爲3,也就是此時position3compact操做後,limit會迴歸到和初始化數組容量大小同樣,並將mark值置爲 -1

咱們來看示意圖,在進行buffer.compact()調用前:

buffer.compact()調用後:

ByteBuffer的其餘方法

接下來,咱們再接觸一些ByteBuffer的其餘方法,方便在適當的條件下進行使用。

rewind方法

首先來看它的源碼:

//java.nio.Buffer#rewind
public Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}
複製代碼

這裏就是將position設定爲0,mark設定爲-1,其餘設定的管理屬性(capacitylimit)不變。結合前面的知識,在字節數組寫入數據後,它的clear方法也只是重置咱們在Buffer中設定的那幾個加強管理屬性(capacitypositionlimitmark),此處的英文表達的意思也很明顯:倒帶,也就是能夠回頭從新寫,或者從新讀。可是咱們要注意一個前提,咱們要確保已經恰當的設置了limit。這個方法能夠在Channel的讀或者寫以前調用,如:

out.write(buf);    // Write remaining data
buf.rewind();      // Rewind buffer
buf.get(array);    // Copy data into array
複製代碼

咱們經過下圖來進行展現執行rewind操做後的結果:

duplicate 方法

在JDK9版本中,新增了這個方法。用來建立一個與原始Buffer同樣的新Buffer。新Buffer的內容和原始Buffer同樣。改變新Buffer內的數據,一樣會體如今原始Buffer上,反之亦然。兩個Buffer都擁有本身獨立的 positionlimitmark 屬性。 剛建立的新Bufferpositionlimitmark 屬性與原始Buffer對應屬性的值相同。 還有一點須要注意的是,若是原始Buffer是隻讀的(即HeapByteBufferR),那麼新Buffer也是隻讀的。若是原始BufferDirectByteBuffer,那新Buffer也是DirectByteBuffer。 咱們來看相關源碼實現:

//java.nio.HeapByteBuffer#duplicate
public ByteBuffer duplicate() {
    return new HeapByteBuffer(hb,
                                this.markValue(),
                                this.position(),
                                this.limit(),
                                this.capacity(),
                                offset);
}
//java.nio.HeapByteBufferR#duplicate
public ByteBuffer duplicate() {
    return new HeapByteBufferR(hb,
                                this.markValue(),
                                this.position(),
                                this.limit(),
                                this.capacity(),
                                offset);
}
//java.nio.DirectByteBuffer#duplicate
public ByteBuffer duplicate() {
    return new DirectByteBuffer(this,
                                    this.markValue(),
                                    this.position(),
                                    this.limit(),
                                    this.capacity(),
                                    0);
}

複製代碼

基本類型的參數傳遞都是值傳遞,因此由上面源碼可知每一個新緩衝區都擁有本身的 positionlimitmark 屬性,並且他們的初始值使用了原始Buffer此時的值。 可是,從HeapByteBuffer角度來講,對於hb 做爲一個數組對象,屬於對象引用傳遞,即新老Buffer共用了同一個字節數組對象。不管誰操做,都會改變另外一個。 從DirectByteBuffer角度來講,直接內存看重的是地址操做,因此,其在建立這個新Buffer的時候傳入的是原始Buffer的引用,進而能夠獲取到相關地址。

asReadOnlyBuffer

可使用 asReadOnlyBuffer() 方法來生成一個只讀的緩衝區。這與 duplicate()實現有些相同,除了這個新的緩衝區不容許使用put(),而且其isReadOnly()函數 將會返回true 。 對這一隻讀緩衝區調用put()操做,會致使ReadOnlyBufferException異常。 咱們來看相關源碼:

//java.nio.ByteBuffer#put(java.nio.ByteBuffer)
public ByteBuffer put(ByteBuffer src) {
    if (src == this)
        throw createSameBufferException();
    if (isReadOnly())
        throw new ReadOnlyBufferException();
    int n = src.remaining();
    if (n > remaining())
        throw new BufferOverflowException();
    for (int i = 0; i < n; i++)
        put(src.get());
    return this;
}
//java.nio.HeapByteBuffer#asReadOnlyBuffer
public ByteBuffer asReadOnlyBuffer() {

    return new HeapByteBufferR(hb,
                                    this.markValue(),
                                    this.position(),
                                    this.limit(),
                                    this.capacity(),
                                    offset);
}
//java.nio.HeapByteBufferR#asReadOnlyBuffer
//HeapByteBufferR下直接調用其duplicate方法便可,其原本就是隻讀的
public ByteBuffer asReadOnlyBuffer() {
    return duplicate();
}
//java.nio.DirectByteBuffer#asReadOnlyBuffer
public ByteBuffer asReadOnlyBuffer() {

    return new DirectByteBufferR(this,
                                        this.markValue(),
                                        this.position(),
                                        this.limit(),
                                        this.capacity(),
                                        0);
}
//java.nio.DirectByteBufferR#asReadOnlyBuffer
public ByteBuffer asReadOnlyBuffer() {
    return duplicate();
}

//java.nio.HeapByteBufferR#HeapByteBufferR
protected HeapByteBufferR(byte[] buf, int mark, int pos, int lim, int cap, int off) {
    super(buf, mark, pos, lim, cap, off);
    this.isReadOnly = true;

}
//java.nio.DirectByteBufferR#DirectByteBufferR
DirectByteBufferR(DirectBuffer db,       
                int mark, int pos, int lim, int cap,
                int off)
{

    super(db, mark, pos, lim, cap, off);
    this.isReadOnly = true;

}
複製代碼

能夠看到,ByteBuffer的只讀實現,在構造器裏首先將isReadOnly屬性設定爲true。接着,HeapByteBufferR繼承了HeapByteBuffer 類(DirectByteBufferR也是相似實現,就不重複了),並重寫了全部可對buffer修改的方法。把全部能修改buffer的方法都直接拋出ReadOnlyBufferException來保證只讀。來看DirectByteBufferR相關源碼,其餘對應實現同樣:

//java.nio.DirectByteBufferR#put(byte)
public ByteBuffer put(byte x) {
    throw new ReadOnlyBufferException();
}
複製代碼
slice 方法

slice從字面意思來看,就是切片,用在這裏,就是分割ByteBuffer。即建立一個從原始ByteBuffer的當前位置(position)開始的新ByteBuffer,而且其容量是原始ByteBuffer的剩餘消費元素數量( limit-position)。這個新ByteBuffer與原始ByteBuffer共享一段數據元素子序列,也就是設定一個offset值,這樣就能夠將一個相對數組第三個位置的元素看做是起點元素,此時新ByteBufferposition就是0,讀取的仍是所傳入這個offset的所在值。分割出來的ByteBuffer也會繼承只讀和直接屬性。 咱們來看相關源碼:

//java.nio.HeapByteBuffer#slice()
public ByteBuffer slice() {
    return new HeapByteBuffer(hb,
                                -1,
                                0,
                                this.remaining(),
                                this.remaining(),
                                this.position() + offset);
}
protected HeapByteBuffer(byte[] buf, int mark, int pos, int lim, int cap, int off) {
    super(mark, pos, lim, cap, buf, off);
    /* hb = buf; offset = off; */
    this.address = ARRAY_BASE_OFFSET + off * ARRAY_INDEX_SCALE;
}
複製代碼

由源碼可知,新ByteBuffer和原始ByteBuffer共有了一個數組,新ByteBuffermark值爲-1,position值爲0,limitcapacity都爲原始Bufferlimit-position的值。 因而,咱們能夠經過下面兩幅圖來展現slice方法先後的對比。

原始ByteBuffer

調用slice方法分割後獲得的新ByteBuffer

本篇到此爲止,在下一篇中,我會着重講下DirectByteBuffer的實現細節。

本文參考及圖片來源:www.jianshu.com/p/12c81abb5…

相關文章
相關標籤/搜索