NIO學習筆記——緩衝區(Buffer)詳解

緩衝區是包在一個對象內的基本數據元素數組,Buffer類相比一個簡單的數組的優點是它將關於數據的數據內容和信息包含在一個單一的對象中。

Buffer的屬性

容量(capacity):緩衝區能夠容納的數據元素的最大數量。這一容量在緩衝區創建時被設定,並且永遠不能被改變
上界(limit):緩衝區的第一個不能被讀或寫的元素。或者說,緩衝區中現存元素的計數
位置(position):下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新
標記(mark):下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新一個備忘位置。調用 mark( )來設定 mark = postion。調用 reset( )設定 position =mark。標記在設定前是未定義的(undefined)。這四個屬性之間總是遵循以下關係:0 <= mark <= position <= limit <= capacity
例如:

// mark=position=0;limit=capacity=256
ByteBuffer buf = ByteBuffer.allocate(256);

Buffer源碼分析

package com.swk.nio;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.InvalidMarkException;
import java.nio.ReadOnlyBufferException;
public abstract class Buffer {
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;
    // Creates a new buffer with the given mark, position, limit, and capacity,
    // after checking invariants.
    //根據mark,position,limit,capacity初始化buffer
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
    /**
     * 返回Buffer的容量
     * @author fuyuwei
     * 2017年6月19日 下午9:23:19
     * @return
     */
    public final int capacity() {
        return capacity;
    }
    /**
     * 返回Buffer的位置
     * @author fuyuwei
     * 2017年6月19日 下午9:23:33
     * @return
     */
    public final int position() {
        return position;
    }
    /**
     * 設置Buffer的position,如果mark>position,則mark廢棄
     * @author fuyuwei
     * 2017年6月19日 下午9:23:56
     * @param newPosition
     * @return
     */
    public final Buffer position(int newPosition) {
        if ((newPosition > limit) || (newPosition < 0))
            throw new IllegalArgumentException();
        position = newPosition;
        if (mark > position) mark = -1;
        return this;
    }
    /**
     * 返回Buffer的上界
     * @author fuyuwei
     * 2017年6月19日 下午9:25:10
     * @return
     */
    public final int limit() {
        return limit;
    }
    /**
     * 設置Buffer的上界,如果position>limit,則把position設置爲新的limit,如果mark>newLimit則廢棄mark
     * @author fuyuwei
     * 2017年6月19日 下午9:25:26
     * @param newLimit
     * @return
     */
    public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        limit = newLimit;
        if (position > limit) position = limit;
        if (mark > limit) mark = -1;
        return this;
    }
    /**
     * 將mark設置爲position
     * @author fuyuwei
     * 2017年6月19日 下午9:28:02
     * @return
     */
    public final Buffer mark() {
        mark = position;
        return this;
    }
    /**
     * 將buffer的position重置爲先前的mark
     * @author fuyuwei
     * 2017年6月19日 下午9:28:32
     * @return
     */
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

    /**
     * 用於寫模式,清空buffer,將position置爲0,limit置爲capacity,丟棄mark
     * 需要注意的此時buffer中仍有數據,當相同或不同線程再訪問時可以直接從內存中獲取
     * @author fuyuwei
     * 2017年6月19日 下午9:29:29
     * @return
     */
    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

    /**
     * 將寫模式轉變爲讀模式;翻轉此buffer,首先對當前位置設置限制,然後將該位置設置爲零。如果已定義了標記,則丟棄該標記
     * @author fuyuwei
     * 2017年6月19日 下午9:31:44
     * @return
     */
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
    /**
     * rewind()在讀寫模式下都可用,它單純的將當前位置置0,同時取消mark標記
     * @author fuyuwei
     * 2017年6月19日 下午9:34:48
     * @return
     */
    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }
    /**
     * 獲取當前位置到上限之間元素個數
     * @author fuyuwei
     * 2017年6月19日 下午9:35:12
     * @return
     */
    public final int remaining() {
        return limit - position;
    }
    /**
     * 是否還有剩餘元素
     * @author fuyuwei
     * 2017年6月19日 下午9:35:51
     * @return
     */
    public final boolean hasRemaining() {
        return position < limit;
    }
    /**
     * 當前buffer是否是隻讀
     * @author fuyuwei
     * 2017年6月19日 下午9:36:16
     * @return
     */
    public abstract boolean isReadOnly();
    /**
     * 判斷這個緩衝區是否被一個可訪問數組所支持
     * @author fuyuwei
     * 2017年6月19日 下午9:38:38
     * @return
     */
    public abstract boolean hasArray();
    /**
     * 返回支持的數組
     * 這種方法的目的是讓數組支持的緩衝區更有效地傳遞給本地代碼。具體的子類爲這個方法提供了更強類型的返回值
     * 對該緩衝區內容的修改將導致返回的數組內容被修改,反之亦然。
     * 在調用這個方法之前,調用@link hasArray hasArray方法,以確保該緩衝區擁有一個可訪問的支持數組。
     * @author fuyuwei
     * 2017年6月19日 下午9:39:11
     * @return
     */
    public abstract Object array();
    /**
     * 在緩衝區的第一個元素的支持數組中返回偏移量
     * 如果這個緩衝區得到一個數組的支持那麼緩衝位置對應於數組索引
     * 在調用這個方法之前調用@link hasArray hasArray方法,以確保該緩衝區具有可訪問的支持數組
     * @author fuyuwei
     * 2017年6月19日 下午9:40:52
     * @return
     */
    public abstract int arrayOffset();
    /**
     * Tells whether or not this buffer is
     * <a href="ByteBuffer.html#direct"><i>direct</i></a>. </p>
     *
     * @return  <tt>true</tt> if, and only if, this buffer is direct
     *
     * @since 1.6
     */
    public abstract boolean isDirect();
    // -- Package-private methods for bounds checking, etc. --
    /**
     * 檢查當前位置與限制,如果它不小於限制,則拋出一個@link BufferUnderflowException,否則增加該位置
     * @author fuyuwei
     * 2017年6月19日 下午9:43:00
     * @return
     */
    final int nextGetIndex() {                          // package-private
        if (position >= limit)
            throw new BufferUnderflowException();
        return position++;
    }
    /**
     * 增加指定值的position
     * @author fuyuwei
     * 2017年6月19日 下午9:44:05
     * @param nb
     * @return
     */
    final int nextGetIndex(int nb) {                    // package-private
        if (limit - position < nb)
            throw new BufferUnderflowException();
        int p = position;
        position += nb;
        return p;
    }
    /**
     * Checks the current position against the limit, throwing a {@link
     * BufferOverflowException} if it is not smaller than the limit, and then
     * increments the position. </p>
     *
     * @return  The current position value, before it is incremented
     */
    final int nextPutIndex() {                          // package-private
        if (position >= limit)
            throw new BufferOverflowException();
        return position++;
    }
    final int nextPutIndex(int nb) {                    // package-private
        if (limit - position < nb)
            throw new BufferOverflowException();
        int p = position;
        position += nb;
        return p;
    }
    /**
     * 檢查給定索引的限制,如果它不小於limit或小於0,則拋出一個@link IndexOutOfBoundsException。
     * @author fuyuwei
     * 2017年6月19日 下午9:45:34
     * @param i
     * @return
     */
    final int checkIndex(int i) {                       // package-private
        if ((i < 0) || (i >= limit))
            throw new IndexOutOfBoundsException();
        return i;
    }
    final int checkIndex(int i, int nb) {               // package-private
        if ((i < 0) || (nb > limit - i))
            throw new IndexOutOfBoundsException();
        return i;
    }
    final int markValue() {                             // package-private
        return mark;
    }
    final void truncate() {                             // package-private
        mark = -1;
        position = 0;
        limit = 0;
        capacity = 0;
    }
    final void discardMark() {                          // package-private
        mark = -1;
    }
    static void checkBounds(int off, int len, int size) { // package-private
        if ((off | len | (off + len) | (size - (off + len))) < 0)
            throw new IndexOutOfBoundsException();
    }
}

Buffer的存取

緩衝區管理着固定數目的數據元素,在我們想清空緩衝區之前,我們可能只使用了緩衝區的一部分。這時,我們需要能夠追蹤添加到緩衝區內的數據元素的數量,放入下一個元素的位置等等的方法。位置屬性做到了這一點。它在調用 put()時指出了下一個數據元素應該被插入的位置,或者當 get()被調用時指出下一個元素應從何處檢索。這裏我們以ByteBuffer爲例介紹下這兩個方法。

public abstract class ByteBuffer {
    /**
     * 讀取該緩衝區當前位置上的字節,然後增加位置。
     * @author fuyuwei
     * 2017年6月19日 下午9:54:42
     * @return
     */
    public abstract byte get();

    /**
     * 讀取給定索引中的字節
     * @author fuyuwei
     * 2017年6月19日 下午9:56:17
     * @param index
     * @return
     */
    public abstract byte get(int index);

    /**
     * 將給定的字節寫入當前位置的緩衝區中,然後增加位置
     * @author fuyuwei
     * 2017年6月19日 下午9:56:54
     * @param b
     * @return
     */
    public abstract ByteBuffer put(byte b);

    /**
     * 在給定索引中將給定字節寫入該緩衝區
     * @author fuyuwei
     * 2017年6月19日 下午9:57:53
     * @param index
     * @param b
     * @return
     */
    public abstract ByteBuffer put(int index, byte b);
}

Buffer的填充

我們將代表“Hello”字符串的 ASCII 碼載入一個名爲 buffer 的ByteBuffer 對象中。

public static void main(String[] args) {
        ByteBuffer buffer=ByteBuffer.allocate(256);
        buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
    }

此時byteBuffer的position爲5(從0開始),注意本例中的每個字符都必須被強制轉換爲 byte。我們不能不經強制轉換而這樣操做:

buffer.put('H');

因爲我們存放的是字節而不是字符。記住在 java 中,字符在內部以 Unicode 碼錶示,每個 Unicode 字符佔 16 位。通過將char 強制轉換爲 byte,我們刪除了前八位來建立一個八位字節值。既然我們已經在 buffer 中存放了一些數據,如果我們想在不丟失位置的情況下通過put進行修改。假設我們想將緩衝區中的內容從“Hello”的 ASCII 碼更改爲“ Mellow”。我們可以這樣實現:

buffer.put(0,(byte)'M').put((byte)'w');

第0個position的H被替換爲了M,而第二個put不會修改第1個position他會從之前記住的position(第5個)開始往buffer裏放數據

Buffer的翻轉

對於已經寫滿了緩衝區,如果將緩衝區內容傳遞給一個通道,以使內容能被全部寫出。但如果通道現在在緩衝區上執行get(),那麼它將從我們剛剛插入的有用數據之外取出未定義數據。如果我們通過翻轉將位置值重新設爲 0,通道就會從正確位置開始獲取。

public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

Flip()函數將一個能夠繼續添加數據元素的填充狀態的緩衝區翻轉成一個準備讀出元素的釋放狀態
例如我們定義了一個容量是10的buffer,並填入hello,如下圖所示
這裏寫圖片描述
翻轉後如下圖所示
這裏寫圖片描述
Rewind()函數與 flip()相似,但不影響上界屬性。它只是將位置值設回 0。您可以使用 rewind()後退,重讀已經被翻轉的緩衝區中的數據。
翻轉兩次把上界設爲位置的值,並把位置設爲 0。上界和位置都變成 0,get()操作會導致 BufferUnderflowException 異常。而 put()則會導致 BufferOverflowException 異常。

Buffer的釋放

如果一個填滿的緩衝區在讀之前要對其進行翻轉,hashRemaining會在釋放緩衝區時告訴我們是否已達到緩衝區的上界。緩衝區並不是線程安全的,多線程環境下在存取緩衝區之前要進行同步處理。一旦緩衝區對象完成填充並釋放,它就可以被重新使用了,clear()將緩衝區重置爲空。他並不改變緩衝區的數據,僅僅是將上界設爲容量值,並把位置設置爲0,這使得緩衝區可以重新被填入。
填充和釋放緩衝區代碼例如:

public class NIOTest {

    private static int index = 0;
    private static String [] strings = {
    "A random string value",
    "The product of an infinite number of monkeys",
    "Hey hey we're the Monkees",
    "Opening act for the Monkees: Jimi Hendrix",
    "'Scuse me while I kiss this fly", 
    "Help Me! Help Me!",
    };
    public static void main(String[] args) {
        CharBuffer buf = CharBuffer.allocate(128);
        while(fillBuffer(buf)){
            buf.flip();
            drainBuffer(buf);
            buf.clear();
        }
    }

    private static void drainBuffer(CharBuffer buffer){
        while(buffer.hasRemaining()){
            System.out.println(buffer.get());
        }
    }

    private static boolean fillBuffer(CharBuffer buffer){
        if(index >= strings.length){
            return false;
        }
        String string = strings[index++];
        for(int i=0;i<strings.length;i++){
            buffer.put(string.charAt(i));
        }
        return true;
    }
}

Buffer的壓縮

public abstract ByteBuffer compact();

如果我們只想從緩衝區中釋放一部分數據,而不是全部,然後重新填充。爲了實現這一點,未讀的數據元素需要下移以使第一個元素索引爲 0。儘管重複這樣做會效率低下,但這有時非常必要,而 API 對此爲您提供了一個 compact()函數。這一緩衝區工具在複製數據時要比您使用 get()和 put()函數高效得多。
這裏寫圖片描述
壓縮後變成
這裏寫圖片描述
元素2-4被複制了0-2。位置3,4不受影響,3和4可以被之後的put覆蓋,postion變成了被複制元素的個數的位置,limit=capacity,此時緩衝區可以再次被填滿。調用 compact()的作用是丟棄已經釋放的數據,保留未釋放的數據,並使緩衝區對重新填充容量準備就緒。

Buffer的標記

標記,使緩衝區能夠記住一個位置並在之後將其返回。緩衝區的標記在 mark( )函數被調用之前是未定義的,調用時標記被設爲當前位置的值。reset( )函數將位置設爲當前的標記值。如果標記值未定義,調用 reset( )將導致 InvalidMarkException 異常。一些緩衝區函數會拋棄已經設定的標記( rewind( ), clear( ),以及 flip( )總是拋棄標記)。如果新設定的值比當前的標記小,調用limit( )或 position( )帶有索引參數的版本會拋棄標記。這裏需要注意的是clear( )函數將清空緩衝區,而 reset( )位置返回到一個先前設定的標記。
這裏寫圖片描述
如果這個緩衝區現在被傳遞給一個通道,兩個字節(“ lo”)將會被髮送,而位置會前進到 5。如果我們此時調用 reset( ),位置將會被設爲標記,如下圖所示。再次將緩衝區傳遞給通道將導致四個字節(“ello”)被髮送。
這裏寫圖片描述

Buffer的比較

equals( )
返回true的條件:
1、兩個對象類型相同。包含不同數據類型的 buffer 永遠不會相等,而且 buffer絕不會等於非 buffer 對象。
2、兩個對象都剩餘同樣數量的元素。 Buffer 的容量不需要相同,而且緩衝區中剩餘數據的索引也不必相同。但每個緩衝區中剩餘元素的數目(從位置到上界)必須相同。
3、在每個緩衝區中應被 Get()函數返回的剩餘數據元素序列必須一致
compareTo( )
compareTo()方法比較兩個Buffer的剩餘元素(byte、char等), 如果滿足下列條件,則認爲一個Buffer“小於”另一個Buffer:
1、第一個不相等的元素小於另一個Buffer中對應的元素 。
2、所有元素都相等,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數比另一個少)。

Buffer的批量移動

緩衝區的涉及目的就是爲了能夠高效傳輸數據。一次移動一個數據元素效率並不高,Buffer的API給我們提供了批量移動的方法。批量移動總是具有指定的長度。也就是說,您總是要求移動固定數量的數據元素

/**
     * 這個方法將所有字節從這個緩衝區傳輸到給定的目標數組中 
     * @author fuyuwei
     * 2017年6月20日 下午9:21:41
     * @param dst
     * @return
     */
    public ByteBuffer get(byte[] dst) {
        return get(dst, 0, dst.length);
    }
/**
     * 這個方法將字節從這個緩衝區傳輸到給定的目標數組。
     * @author fuyuwei
     * 2017年6月20日 下午9:23:01
     * @param dst
     * @param offset
     * @param length
     * @return
     */
    public ByteBuffer get(byte[] dst, int offset, int length) {
        checkBounds(offset, length, dst.length);
        if (length > remaining())
            throw new BufferUnderflowException();
        int end = offset + length;
        for (int i = offset; i < end; i++)
            dst[i] = get();
        return this;
    }
/**
     * 這個方法將給定源字節數組的整個內容傳輸到這個緩衝區中。
     * @author fuyuwei
     * 2017年6月20日 下午9:26:33
     * @param src
     * @return
     */
     public final ByteBuffer put(byte[] src) {
            return put(src, 0, src.length);
     }
/**
     * 這個方法將字節從給定的源數組中轉移到這個緩衝區中
     * @author fuyuwei
     * 2017年6月20日 下午9:30:52
     * @param src
     * @param offset
     * @param length
     * @return
     */
    public ByteBuffer put(byte[] src, int offset, int length) {
        checkBounds(offset, length, src.length);
        if (length > remaining())
            throw new BufferOverflowException();
        int end = offset + length;
        for (int i = offset; i < end; i++)
            this.put(src[i]);
        return this;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章