ByteBuffer閱讀筆記
問題
一、簡介
是一個抽象類,但是可以根據類提供的靜態方法,來生成堆內或堆外的實例對象,Buffer的第一級子類
採用了《模板模式》的設計模式
二、繼承關係圖
- 相關實現
我們通過IDEA的插件可以看出ByteBuffer相關的繼承關係及實現類,我們下面簡單描述一下
- HeapByteBuffer:繼承與ByteBuffer,數據存儲在 JVM中間緩存區
- HeapByteBufferR:繼承於HeapByteBuffer,堆緩存區,只支持只讀
- MappedByteBuffer:繼承於ByteBuffer,是一個抽象類
- 是Java NIO中引入的一種硬盤物理文件和內存映射方式,當物理文件較大,採用MappedByteBuffer,讀寫性能較高,其內部的核心實現了DirectByteBuffer(JVM堆外直接物理內存)
- JVM進程通過內存映射方式加載的物理文件並不會耗費同等大小的物理內存,當應用程序訪問數據時,程序通過虛擬地址尋址對應的內存頁,如果物理內存中不存在對應頁,MMU則會產生缺頁中斷異常,CPU嘗試從系統Swap分區中查找,如仍不存在,則會直接從硬盤中物理文件中讀取。
- 所以MappedByteBuffer使用虛擬內存,因此分配(map)的內存大小不受JVM的-Xmx參數限制,但是也是有大小限制的。 如果當文件超出1.5G限制時,可以通過position參數重新map文件後面的內容。
- DirectByteBuffer:繼承於MappedByteBuffer,也稱呼爲JVM堆外直接物理內存。因爲數據存儲在JVM堆外的直接物理內存中
- DirectByteBufferR:繼承於DirectByBuffer,物理內存緩衝區,僅支持讀
三、存儲結構
四、源碼分析
內部類
屬性
/** 不爲空,僅用於JVM堆緩存區 */
final byte[] hb; // Non-null only for heap buffers
/** 數組的偏移量 */
final int offset;
/** 是否僅支持讀,已R結尾的就是僅支持讀 */
boolean isReadOnly;
// 是否採用默認排序
// 默認true爲ByteOrder.BIG_ENDIAN,將字節排序的順序按照高位開始
// false ByteOrder.LITTLE_ENDIAN,講字節排序的順序按照低位開始
// 統一字節的排序,不然會導致數據讀取異常
boolean bigEndian = true; // package-private
// 本緩存的字節數據排列的順序
boolean nativeByteOrder = (Bits.byteOrder() == ByteOrder.BIG_ENDIAN);// package-private
構造
- 構造方法都是隻能同包或子類使用,可以通過靜態方法實例化不同的子類對象
/** 僅用於HeapByteBuffer實例化時使用 */
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset){
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
/** 僅用於MappedByteBuffer實例化使用 */
ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
// 不需要堆數組,因爲是直接操作物理內存
this(mark, pos, lim, cap, null, 0);
}
主要方法
1、創建緩存區
-
根據選擇不同創建JVM堆內緩存區或堆外直接物理內存。都是靜態方法
/** * 創建堆外直接物理內存 緩存區(僅Byte支持) */ public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); } /** * 創建JVM堆內 緩存區,自定義容量 */ public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); } /** * 創建JVM堆內 緩存區 * 自定義數組,offset其實讀寫索引,length 支持讀寫長度 */ public static ByteBuffer wrap(byte[] array, int offset, int length){ try { return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } } /** * 創建JVM堆內 緩存區, * 自定義數組,默認offset爲0,length 爲數組的長度 */ public static ByteBuffer wrap(byte[] array) { return wrap(array, 0, array.length); }
2、get方法
-
2個
/** * dst,用於存儲讀取內容 * offset,讀取內容寫入到dst數組的其實索引 * length,本次讀取的數據大小 */ public ByteBuffer get(byte[] dst, int offset, int length) { // Buffer的邊界檢測,檢測dst是否有足夠存放容量的空間 checkBounds(offset, length, dst.length); // 如果讀取的大小 大於剩餘的容量大小,則拋出BufferUnderflowException異常 if (length > remaining()) throw new BufferUnderflowException(); // 本次讀取結束索引位 int end = offset + length; for (int i = offset; i < end; i++) // 模板模式,根據不同的構造調用不同的get方法 dst[i] = get(); return this; } /** * 直接獲取dst數組大小的數據,但是如果剩餘的數量小於dst.length 則會拋出異常哦 */ public ByteBuffer get(byte[] dst) { // 調用的是上面的方法,直接默認從0下入,寫入數組的最大長度 return get(dst, 0, dst.length); }
3、put方法
-
3個
/** * 讀取緩存塊(堆內或堆外)中的數據到自身,僅支持數據寫入到JVM緩存快 */ public ByteBuffer put(ByteBuffer src) { // 參數效驗 // 1.不可是自身 // 2.不可是隻讀(也就是通過asReadOnlyBuffer方法獲取的只讀緩衝區) // 3.待讀取的src緩存快 剩餘可讀取的容量 不可大於自身 可寫入的容量 if (src == this) throw new IllegalArgumentException(); if (isReadOnly()) throw new ReadOnlyBufferException(); int n = src.remaining(); if (n > remaining()) throw new BufferOverflowException(); // 開始讀取src中的數據,寫入到自身 for (int i = 0; i < n; i++) put(src.get()); return this; } /** * src 待讀取的數組 * offset 讀取的開始位置 * length 本次讀取的長度 */ public ByteBuffer put(byte[] src, int offset, int length) { // 參數效驗,是否超出src的可讀寫邊界 checkBounds(offset, length, src.length); // 從數組中讀取的容量大小不可超過緩存快剩餘可寫入的容量大小 if (length > remaining()) throw new BufferOverflowException(); int end = offset + length; // 開始讀取scr中的數組,寫入到自身 for (int i = offset; i < end; i++) this.put(src[i]); return this; } public final ByteBuffer put(byte[] src) { // 調用的上面的方法,直接默認從0開始,讀取數組的最大長度 return put(src, 0, src.length); }
4、其他方法
例如:一下XXX代表Long、Char、Int等
-
slice()把剩餘的可操作容量生成一個獨立buffer,底層數組是共享的,只是操作區不同
-
arrayOffSet()獲取當前position所在的數組索引位
-
putXXX(xxx) 在當前position插入當前類型數據,例如:char類型,就插入2個字節
-
putXXX(int,xxx) 在指定的位置插入xxx數據
-
asXXXBuffer 創建只讀緩存區,把xxxBuffer自身作爲xxxBufferR的屬性對象
-
order 獲取當前buffer的字節排列順序,高位排列還是地位排列
-
order(ByteOrder)設置當前字符的排序順序,BIG_ENDIAN或LITTLE_ENDIAN
-
compact() 壓縮緩存區,將緩存區的當前位置(position)和限制(limit)之間的字節(如果有)賦值到緩衝區開始處(0)。
-
equals(Object)比較,比較的是position和limit之間的數據、數量是否一致
-
compareTo(Object)比較兩個從position之後的數據是否不一樣,不一樣的時候就用自己的position數據-傳入的position數據,自己大就返回正數,否則負數,如果遍歷了最小的剩餘數量還沒有結果,就看兩個buffer的剩餘數量誰大。所以如果結果等於0,那麼類似equals,可用於排序比較哈
-
duplicate() 賦值此緩存區,4大基本屬性mark、position、limit、capacity、數組、offset都一起復制
-
還有前天的常用方法和抽象方法,有興趣的可以去看
補充-關於CharBuffer和ByteBuffer轉換及解決編碼問題
-
關於byte轉換爲中文,需要考慮編碼問題,需要利用CharSets
public classs demo { public static void main(String[] args) { utf8(); utf16be(); } private static void utf8() { byte[] bytes = "我是中國人".getBytes(StandardCharsets.UTF_8); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); System.out.println(byteBuffer.toString()); CharBuffer charBuffer = Charset.forName("utf-8").decode(byteBuffer); for (int i = 0; i < charBuffer.limit(); i++) { System.out.print(charBuffer.get()); } System.out.println(); } private static void utf16be() { byte[] bytes = "我是中國人".getBytes(StandardCharsets.UTF_16BE); ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); System.out.println(byteBuffer.toString()); CharBuffer charBuffer = Charset.forName("utf_16be").decode(byteBuffer); for (int i = 0; i < charBuffer.limit(); i++) { System.out.print(charBuffer.get()); } System.out.println(); } }
五、總結
buffer沒有擴容方法,所以可以自己實現一個,然後把原來的copy過去到新的即可