Java NIO學習篇之緩衝區Buffer詳解

定義

緩衝區Buffer在java nio中負責數據的存儲,緩衝區就是數組,用於存儲不同類型數據的數組。

jdk爲java七大基本類型數據都準備了響應的緩衝區(boolean值除外):

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntegerBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

上述緩衝區除了ByteBuffer的 功能稍微多點外,因爲ByteBuffer是通用的,所以功能會比較多。其他6種的使用方式幾乎是一致的。

/*
* Buffer以上七大基本類型緩衝區類的基類,是個抽象類,定義了一些通用方法以及一些抽象方法。
* 具體方法都是final修飾的,不可被子類覆蓋。
*
public abstract class Buffer {

    /**
     * 不知道啥用
     */
    static final int SPLITERATOR_CHARACTERISTICS =
        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

    //標記當前的position 位置,用reset方法的話,可以把position設置爲mark值位置。
	//如果mark=-1的話會reset失敗。
    private int mark = -1;
    //遊標,標記緩衝區下一個要讀取或者寫入的數組下標。
    private int position = 0;
    //表示緩衝區數組可以操作(讀取、寫入)的可用數據長度。
    private int limit;
    //緩衝區容量
    private int capacity;

	//以上四個屬性符合 mark<=position<=limit<=capacity,不符合就會報錯。

    // 只對直接緩衝區有用
    // 記錄直接緩衝區的內存地址,用於提高JNI 的方法GetDirectBufferAddress的效率。
    //ByteBuffer才支持直接緩衝區,所以到後面關於ByteBuffer的會講。
    long address;

    // 傳入mark、position、limit、capacity值創建一個緩衝區的構造器
    //是個包內私有的構造器
    Buffer(int mark, int pos, int lim, int cap) { 
         //緩衝區容量小於0時拋出異常。
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);

		//根據傳參設置緩衝區大小。
        this.capacity = cap;
        //設置limit
        limit(lim);
        //設置position
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                //mark大於pos並且mark大於等於0時會拋出異常
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            //設置mark
            this.mark = mark;
        }
    }

    /**
     * 返回緩衝區容量
     */
    public final int capacity() {
        return capacity;
    }

    /**
     * 返回緩衝區下一次要讀取或者寫入的數組下標
     */
    public final int position() {
        return position;
    }

    /**
     * 設置緩衝區的遊標
     */
    public final Buffer position(int newPosition) {
        //要設置的position值不能大於limit值或者小於0,否則就拋出異常。
        if ((newPosition > limit) || (newPosition < 0))
            throw new IllegalArgumentException();
        //設置遊標
        position = newPosition;
        //如果設置的新的遊標值比mark值小,就重置mark值爲-1.
        if (mark > position) mark = -1;
        return this;
    }

    /**
     *返回緩衝區的可用數據長度。
     */
    public final int limit() {
        return limit;
    }

    /**
     * 設置緩衝區的limit值,同時會保證position和mark值保證這幾個值的關係符合規則。
     */
    public final Buffer limit(int newLimit) {
    	//對要設置的新limit進行檢查,如果大於緩衝區容量或者小於0就拋出異常
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        //設置limit值
        limit = newLimit;
        //如果遊標比可操作的最大數組下標還大的話,就把遊標設置爲limitif (position > limit) position = limit;
        //如果mark > limit,重置mark。
        if (mark > limit) mark = -1;
        return this;
    }

    /**
     * 標記當前position位置。
     */
    public final Buffer mark() {
        mark = position;
        return this;
    }

    /**
     * 把遊標恢復到標記的位置
     */
    public final Buffer reset() {
        int m = mark;
        //標記小於0時拋出異常
        if (m < 0)
            throw new InvalidMarkException();
        //重置遊標至標記處
        position = m;
        return this;
    }

    /**
     * 清空緩衝區,其實就設置遊標爲0,limit設置回capacity值,mark重置,數據還是存在的。
     */
    public final Buffer clear() {
    	
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

    /**
     * 緩衝區創建時默認是寫模式的,這個方法把緩衝區改爲讀模式。
     * 每次通過通道往存儲設備中寫數據都需要調用此方法把緩衝區設置爲讀模式。讀取緩衝區的數據。
     * 最後詳細講解這個
     */
    public final Buffer flip() {
        //把limit設置爲position
        limit = position;
        //將遊標設置爲0
        position = 0;
        //重置mark。
        mark = -1;
        return this;
    }

    /**
     * 重置遊標,從新開始讀、寫數據。
     */
    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

    /**
     * 讀模式下,返回剩餘可讀的數據長度,寫模式下,返回剩餘可寫的緩衝區長度。
     */
    public final int remaining() {
        return limit - position;
    }

    /**
     * 返回是否還有數據可讀或者可寫。
     */
    public final boolean hasRemaining() {
        return position < limit;
    }

    /**
     * 返回緩衝區是否只讀,抽象方法,具體看實現類。
     */
    public abstract boolean isReadOnly();

    /**
   
    /**
     * 是否直接緩衝區,true爲直接緩衝區
     */
    public abstract boolean isDirect();


    //////////////////分割線,上面是公共方法API,下面是包私有方法,我們不能調用的//////

    /**
     * 讀模式下游標往右移一位,也就是跳過一個數據。
     * 返回移動前的遊標值。
     */
    final int nextGetIndex() {                          // package-private
    	//因爲遊標加一 所以要確保遊標加一前要小於等於limit
        if (position >= limit)
        	//當前遊標>=limit拋出異常
            throw new BufferUnderflowException();
        return position++;
    }

	/**
	* 讀模式下游標往右移動n位。
	* 返回移動前的遊標
	*/
    final int nextGetIndex(int nb) {                    // package-private
        if (limit - position < nb)
        	//判斷加n後的遊標是否大於limit,大於就拋出異常
            throw new BufferUnderflowException();
        int p = position;
        position += nb;
        //返回移動前的遊標。
        return p;
    }

    /**
     * 寫模式下游標往右移動1位。
	 * 返回移動前的遊標
	 * 邏輯與上面的一樣。
     */
    final int nextPutIndex() {                          // package-private
        if (position >= limit)
            throw new BufferOverflowException();
        return position++;
    }

     /**
     * 寫模式下游標往右移動n位。
	 * 返回移動前的遊標
	 * 邏輯與上面的一樣。
     *
    final int nextPutIndex(int nb) {                    // package-private
        if (limit - position < nb)
            throw new BufferOverflowException();
        int p = position;
        position += nb;
        return p;
    }

    

    final int markValue() {                             // package-private
    	//返回標記的值
        return mark;
    }

    //把緩衝區置爲不可用
    final void truncate() {                             // package-private
    	
    	//重置mark爲-1
        mark = -1;
        //啥都置零
        position = 0;
        limit = 0;
        capacity = 0;
    }

    final void discardMark() {                          // package-private
        //重置遊標-1
        mark = -1;
    }

    
}

limit、position與緩衝區的讀寫模式:

Buffer有分讀模式和寫模式,其實質是由limit值和position值決定的。這種模式沒有特定的死規定。

寫模式:
往緩衝區Buffer裏面填充數據的模式。Buffer剛剛創建好時就是處於這個模式。

public class ByteBufferDemo {

    public static void main(String[] args) {
    	//創建一個10和字節大小的字節緩衝區。
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        System.out.println("capacity = " + byteBuffer.capacity());
        System.out.println("limit = " + byteBuffer.limit());
        System.out.println("position = " + byteBuffer.position());
    }
}

結果:
在這裏插入圖片描述
相應的圖:
在這裏插入圖片描述
解釋:
此時的遊標position=0,limit=10,所以可以通過put方法(此方法在各自子類中申明定義,基類沒有申明)往Buffer中寫入n個數據。position也會相應右移n爲,但是n必須符合n<=limit-position才行。否則會拋出異常。

寫入5個數據後:

public class ByteBufferDemo {

    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        //寫入5個數據。
        byteBuffer.put("hello".getBytes());
        System.out.println("capacity = " + byteBuffer.capacity());
        System.out.println("limit = " + byteBuffer.limit());
        System.out.println("position = " + byteBuffer.position());
    }
}

執行結果:
在這裏插入圖片描述
相應的圖:
在這裏插入圖片描述
解釋:
因爲寫入了5個數據,所以position=5,繼續寫入數據會從緩衝區數組的下標5開始寫入,此時剩餘的可寫數據長度爲limit-position=5。

此時,數據寫入完成,用通道channel讀取緩衝區數據到設備上,通道channel讀取緩衝區數據到設備上會把緩衝區的數組從position值開始讀,一直讀到limit前結束。

假設我們沒有把緩衝區從寫模式切換到讀模式,通道channel讀取緩衝區數據時就會從position=5的下標讀取到limit=10前的9下標。也就是讀取緩衝區數組的5-9下標,這些下標的元素是沒有被寫入的,所以是錯誤的。

public class ByteBufferDemo {

    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put("hello".getBytes());
        byte[] bytes = new byte[byteBuffer.remaining()];
        //讀取緩衝區,這裏就不往設備上讀了
        byteBuffer.get(bytes);
        System.out.println(Arrays.toString(bytes));
    }
}

執行結果:
在這裏插入圖片描述
解釋:
讀取的是後面的沒有被寫入的緩衝區數組區域,是錯誤的。

所以要把緩衝區切換爲讀模式:
讀模式:從緩衝區數組中讀取緩衝區數組數據的模式。


public class ByteBufferDemo {

    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put("hello".getBytes());
        //切換爲讀模式
        byteBuffer.flip();
        System.out.println("capacity = " + byteBuffer.capacity());
        System.out.println("limit = " + byteBuffer.limit());
        System.out.println("position = " + byteBuffer.position());
    }
}

//切換爲讀模式前,limit=10 position=5
//從上面的flip方法的源碼可知,該方法把limit值設置爲position值,把position設置爲0.
//所以調用flip方法後應該是limit=5 position=0.

執行結果:
在這裏插入圖片描述
相應圖:
在這裏插入圖片描述
此時處於讀模式,如果讀取緩衝區的數組從position讀取到limit前一個下標,就會讀取下標0-4的數據,這樣就是正確的讀取。所以每次進行數據讀取時都要把緩衝區切換爲讀模式才能正確地讀取數據。

public class ByteBufferDemo {

    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        byteBuffer.put("hello".getBytes());
        byteBuffer.flip();
        byte[] bytes = new byte[byteBuffer.remaining()];
        byteBuffer.get(bytes);
        System.out.println(Arrays.toString(bytes));
        System.out.println("==============>讀取數據後");
        System.out.println("capacity = " + byteBuffer.capacity());
        System.out.println("limit = " + byteBuffer.limit());
        System.out.println("position = " + byteBuffer.position());

        //讀完後要情況緩衝區,重寫開始寫數據
        System.out.println("==============>清空緩衝區後");
        byteBuffer.clear();
        System.out.println("capacity = " + byteBuffer.capacity());
        System.out.println("limit = " + byteBuffer.limit());
        System.out.println("position = " + byteBuffer.position());
    }
}


執行結果:
在這裏插入圖片描述
解釋:

  1. 讀到的數據不爲0,正確地讀到了數據。
  2. 讀取數據後,緩衝區將變得不可用,因爲limit-position=0,不能寫也不能繼續讀。
  3. 需要把緩衝區清空後恢復到寫模式來繼續寫數據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章