NIO三大核心之Buffer緩衝區
NIO核心一:緩衝區(Buffer)
緩衝區(Buffer)
一個用於特定基本資料類 型的容器。由 java.nio 包定義的,所有緩衝區 都是 Buffer 抽象類的子類.。Java NIO 中的 Buffer 主要用於與 NIO 通道進行 互動,資料是從通道讀入緩衝區,從緩衝區寫入通道中的
Buffer 類及其子類
Buffer 就像一個數組,可以儲存多個相同型別的資料。根 據資料型別不同 ,有以下 Buffer 常用子類:
-
ByteBuffer
-
CharBuffer
-
ShortBuffer
-
IntBuffer
-
LongBuffer
-
FloatBuffer
-
DoubleBuffer
上述 Buffer 類 他們都採用相似的方法進行管理資料,只是各自 管理的資料型別不同而已。都是通過如下方法獲取一個 Buffer 物件:
static XxxBuffer allocate(int capacity) : 建立一個容量為capacity 的 XxxBuffer 物件
緩衝區的基本屬性
Buffer 中的重要概念:
-
容量 (capacity) :作為一個記憶體塊,Buffer具有一定的固定大小,也稱為"容量",緩衝區容量不能為負,並且建立後不能更改。
-
限制 (limit):表示緩衝區中可以操作資料的大小(limit 後資料不能進行讀寫)。緩衝區的限制不能為負,並且不能大於其容量。 寫入模式,限制等於buffer的容量。讀取模式下,limit等於寫入的資料量
-
位置 (position):下一個要讀取或寫入的資料的索引。緩衝區的位置不能為 負,並且不能大於其限制
-
標記 (mark)與重置 (reset):標記是一個索引,通過 Buffer 中的 mark() 方法 指定 Buffer 中一個特定的 position,之後可以通過呼叫 reset() 方法恢復到這 個 position. 標記、位置、限制、容量遵守以下不變式: 0 <= mark <= position <= limit <= capacity
-
圖示:
Buffer常見方法
Buffer clear() 清空緩衝區並返回對緩衝區的引用 Buffer flip() 為 將緩衝區的界限設定為當前位置,並將當前位置充值為0 int capacity() 返回 Buffer 的 capacity 大小 boolean hasRemaining() 判斷緩衝區中是否還有元素 int limit() 返回 Buffer 的界限(limit) 的位置 Buffer limit(int n) 將設定緩衝區界限為 n, 並返回一個具有新 limit 的緩衝區物件 Buffer mark() 對緩衝區設定標記 int position() 返回緩衝區的當前位置 position Buffer position(int n) 將設定緩衝區的當前位置為 n , 並返回修改後的 Buffer 物件 int remaining() 返回 position 和 limit 之間的元素個數 Buffer reset() 將位置 position 轉到以前設定的 mark 所在的位置 Buffer rewind() 將位置設為為 0, 取消設定的 mark
緩衝區的資料操作
Buffer 所有子類提供了兩個用於資料操作的方法:get()put() 方法 獲取Buffer中的資料 get() :讀取單個位元組 get(byte[] dst):批量讀取多個位元組到 dst 中 get(int index):讀取指定索引位置的位元組(不會移動 position) 放入資料到Buffer中 put(byte b):將給定單個位元組寫入緩衝區的當前位置 put(byte[] src):將 src 中的位元組寫入緩衝區的當前位置 put(int index, byte b):將指定位元組寫入緩衝區的索引位置(不會移動 position)
使用Buffer讀寫資料一般遵循以下四個步驟:
-
1.寫入資料到Buffer
-
2.呼叫flip()方法,轉換為讀取模式
-
3.從Buffer中讀取資料
-
4.呼叫buffer.clear()方法或者buffer.compact()方法清除緩衝區
案例演示
public class TestBuffer { @Test public void test3(){ //分配直接緩衝區 ByteBuffer buf = ByteBuffer.allocateDirect(1024); System.out.println(buf.isDirect()); } @Test public void test2(){ String str = "chihsien"; ByteBuffer buf = ByteBuffer.allocate(1024); buf.put(str.getBytes()); buf.flip(); byte[] dst = new byte[buf.limit()]; buf.get(dst, 0, 2); System.out.println(new String(dst, 0, 2)); System.out.println(buf.position()); //mark() : 標記 buf.mark(); buf.get(dst, 2, 2); System.out.println(new String(dst, 2, 2)); System.out.println(buf.position()); //reset() : 恢復到 mark 的位置 buf.reset(); System.out.println(buf.position()); //判斷緩衝區中是否還有剩餘資料 if(buf.hasRemaining()){ //獲取緩衝區中可以操作的數量 System.out.println(buf.remaining()); } } @Test public void test1(){ String str = "itheima"; //1. 分配一個指定大小的緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("-----------------allocate()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //2. 利用 put() 存入資料到緩衝區中 buf.put(str.getBytes()); System.out.println("-----------------put()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //3. 切換讀取資料模式 buf.flip(); System.out.println("-----------------flip()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //4. 利用 get() 讀取緩衝區中的資料 byte[] dst = new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst, 0, dst.length)); System.out.println("-----------------get()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //5. rewind() : 可重複讀 buf.rewind(); System.out.println("-----------------rewind()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //6. clear() : 清空緩衝區. 但是緩衝區中的資料依然存在,但是處於“被遺忘”狀態 buf.clear(); System.out.println("-----------------clear()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); System.out.println((char)buf.get()); } }
直接與非直接緩衝區
什麼是直接記憶體與非直接記憶體
根據官方文件的描述:
byte buffer
可以是兩種型別,一種是基於直接記憶體(也就是非堆記憶體);另一種是非直接記憶體(也就是堆記憶體)。對於直接記憶體來說,JVM將會在IO操作上具有更高的效能,因為它直接作用於本地系統的IO操作。而非直接記憶體,也就是堆記憶體中的資料,如果要作IO操作,會先從本程序記憶體複製到直接記憶體,再利用本地IO處理。
從資料流的角度,非直接記憶體是下面這樣的作用鏈:
本地IO-->直接記憶體-->非直接記憶體-->直接記憶體-->本地IO
而直接記憶體是:
本地IO-->直接記憶體-->本地IO
很明顯,在做IO處理時,比如網路傳送大量資料時,直接記憶體會具有更高的效率。直接記憶體使用allocateDirect建立,但是它比申請普通的堆記憶體需要耗費更高的效能。不過,這部分的資料是在JVM之外的,因此它不會佔用應用的記憶體。所以呢,當你有很大的資料要快取,並且它的生命週期又很長,那麼就比較適合使用直接記憶體。只是一般來說,如果不是能帶來很明顯的效能提升,還是推薦直接使用堆記憶體。可通過呼叫其 isDirect() 方法來確定位元組緩衝區是直接緩衝區還是非直接緩衝區。
直接緩衝區使用場景
-
1 有很大的資料需要儲存,它的生命週期又很長
-
2 適合頻繁的IO操作,比如網路併發場景