什么是緩沖區(Buffer)
定義
簡單地說就是一塊存儲區域,哈哈哈,可能太簡單了,或者可以換種說法,從代碼的角度來講(可以查看JDK中Buffer、ByteBuffer、DoubleBuffer等的源碼),Buffer類內部其實就是一個基本數據類型的數組,以及對這個緩沖數組的各種操作;
常見的緩沖區如ByteBuffer、IntBuffer、DoubleBuffer...內部對應的數組依次是byte、int、double...
與通道的關系
在Java NIO中,緩沖區主要是跟通道(Channel)打交道,數據總是從緩沖區寫入到通道中,或者從通道讀取數據到緩沖區;
繼承結構
關於Buffer的繼承結構,我們可以簡單的以ByteBuffer為例,如下:
Buffer是頂層抽象類,ByteBuffer繼承Buffer,也是抽象類,ByteBuffer最常見的兩個具體實現類如下:
DirectByteBuffer(JVM堆外部、通過unsafe.allocateMemory實現)、HeapByteBuffer(JVM堆)
緩沖區的四個屬性(capacity、limit、position、mark)
容量(capacity)
capacity指的是緩沖區能夠容納元素的最大數量,這個值在緩沖區創建時被設定,而且不能夠改變,如下,我們創建了一個最大容量為10的字節緩沖區;
ByteBuffer bf = ByteBuffer.allocate(10);
上界(limit)
limit指的是緩沖區中第一個不能讀寫的元素的數組下標索引,也可以認為是緩沖區中實際元素的數量;
位置(position)
position指的是下一個要被讀寫的元素的數組下標索引,該值會隨get()和put()的調用自動更新;
標記(mark)
一個備忘位置,調用mark()方法的話,mark值將存儲當前position的值,等下次調用reset()方法時,會設定position的值為之前的標記值;
四個屬性值之間的關系
根據以上四個屬性的定義,我們可以總結出它們之間的關系如下:
0 <= mark <= position <= limit <= capacity
舉個例子,觀察四個屬性值的變化
1、創建一個容量大小為10的字符緩沖區
ByteBuffer bf = ByteBuffer.allocate(10);
此時:mark = -1; position = 0; limit = 10; capacity = 10;
2、往緩沖區中put()五個字節
bf.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
注意這里一個字符是占用兩個字節的,但是英文字符只占用一個字節,所以這樣是可以實現儲存效果的;
此時:mark = -1; position = 5; limit = 10; capacity = 10;
3、調用flip()方法,切換為讀就緒狀態
bf.flip();
此時:mark = -1; position = 0; limit = 5; capacity = 10;
4、讀取兩個元素
System.out.println("" + (char) bf.get() + (char) bf.get());
此時:mark = -1; position = 2; limit = 5; capacity = 10;
5、標記此時的position位置
bf.mark();
此時:mark = 2; position = 2; limit = 5; capacity = 10;
6、讀取兩個元素后,恢復到之前mark的位置處
System.out.println("" + (char) bf.get() + (char) bf.get()); bf.reset();
屬性變化情況:
執行完第一行代碼:mark = 2; position = 4; limit = 5; capacity = 10;
執行完第二行代碼:mark = 2; position = 2; limit = 5; capacity = 10;
7、調用compact()方法,釋放已讀數據的空間,准備重新填充緩存區
bf.compact();
此時:mark = 2; position = 3; limit = 10; capacity = 10;
注意觀察數組中元素的變化,實際上進行了數組拷貝,拋棄了已讀字節元素,保留了未讀字節元素;
緩沖區比較
其實查看equals源碼就可以知道是如何比較的,如下(以ByteBuffer為例):
public boolean equals(Object ob) { if (this == ob) return true; if (!(ob instanceof ByteBuffer)) return false; ByteBuffer that = (ByteBuffer)ob; if (this.remaining() != that.remaining()) return false; int p = this.position(); for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--) if (!equals(this.get(i), that.get(j))) return false; return true; }
總的來說,兩個緩沖區被認為相等的條件如下(以下內容直接摘自《Java NIO》):
- 兩個對象類型相同。包含不同數據類型的 buffer 永遠不會相等,而且 buffer絕不會等於非 buffer 對象。
- 兩個對象都剩余同樣數量的元素。Buffer 的容量不需要相同,而且緩沖區中剩余數據的索引也不必相同。但每個緩沖區中剩余元素的數目(從位置到上界)必須相同。
- 在每個緩沖區中應被 Get()方法返回的剩余數據元素序列必須一致。
批量讀寫緩沖區數據
以ByteBuffer為例,使用如下API即可:
public ByteBuffer get(byte[] dst, int offset, int length)
public ByteBuffer put(byte[] src, int offset, int length)
public ByteBuffer get(byte[] dst)
public final ByteBuffer put(byte[] src)
實際上,后面兩種方法內部就是調用前面兩種方法的;
參數的含義直接查看源碼注釋即可,寫的很清楚,如put(byte[] src, int offset, int length)方法的注釋:
/* @param src * The array from which bytes are to be read * * @param offset * The offset within the array of the first byte to be read; * must be non-negative and no larger than <tt>array.length</tt> * * @param length * The number of bytes to be read from the given array; * must be non-negative and no larger than * <tt>array.length - offset</tt> */
參考資料
《Java NIO》