1. 程式人生 > 其它 >NIO三大核心之Buffer緩衝區

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操作,比如網路併發場景