Java NIO ———— Buffer 缓冲区详解
引言
緩沖區是一個用于特定基本類型的容器。由java.nio 包定義,所有緩沖區都是 Buffer 抽象類的子類。
Java NIO 中的 Buffer ,主要用于與NIO 通道進行交互。數據從通道存入緩沖區,從緩沖區取出到通道中。
一、創建緩沖區
緩沖區的本質是?數組?,用于存儲不同類型的數據,根據數據類型(boolean 除外),提供了相應類型的緩沖區,如ByteBuffer、IntBuffer等。這些緩沖區的管理方式都是類似的,都是通過 allocate()?方法指定容量并創建緩沖區。
// 創建一個 1 KB 大小的緩沖區 ByteBuffer buf = ByteBuffer.allocate(1024);一般情況下,我們通過 allocate() 方法創建緩沖區,但是在需要高性能的地方,有時候往往需要使用?allocateDirect() 方法。
allocate() 創建非直接緩沖區,allocateDirect() 創建直接緩沖區。
二、緩沖區的四個核心屬性
緩沖區的本質實際上是一個數組,最常用的ByteBuffer,本身就是一個 byte[] 數組,根據數據讀取的場景,設計者為Buffer 設置了四個核心屬性,定義在 Buffer 抽象類中:
private int mark = -1; private int position = 0; private int limit; private int capacity;緩沖區的操作實際上是借由這四個 int 標記來完成的,可以理解為抽象的指針。它們的關系如下:
0 <= mark <= position <= limit <= capacity
position 表示位置,表示當前程序正在操作的數據的下一個索引值。
mark 表示標記,通過 mark() 方法,記錄當前數據的索引。可以通過 reset() 重新找到 mark 所指向的數據。
limit 界限,表示緩沖區中可以操作數據的大小,limit 后的數據不能進行讀寫。
capacity 緩沖區容量,因為緩沖區本身就是數組,因此一旦聲明不能改變該值。
2.1 初始的指針狀態
假設我們聲明了一個 capacity 為 5 的字節緩沖區:
ByteBuffer buf = ByteBuffer.allocate(5);那么,緩沖區的初始狀態就是如下圖所示:
2.2 當緩沖區中有數據的狀態
由于緩沖區獨特的構造,在讀和寫的時候,limit 與 position 指針是有一定區別的。
// 寫模式 byteBuffer.put("Tom".getBytes()); // 讀模式 byteBuffer.get();?三、緩沖區的核心方法
3.1 存取數據
緩沖區既然作為數據的容器,必然涉及到數據的存取操作,但要注意,存和取操作不可以連續執行,兩個動作之間需要有一個 “翻轉” 的操作。
put() 方法將數據放入到緩沖區中;get() 方法從緩沖區中取出數據。
3.2 flip()翻轉、rewind()倒帶、clear()清空
flip() : 翻轉,將緩沖區進行讀寫切換。
rewind() : 倒帶,可以將 position 和 limit 回退到上一次操作前。
clear() : 清空緩沖區,官方說明是“clears the buffer”,但詳細解釋是將 position 和 limit 恢復“出廠設置”,并丟棄 mark。注意,緩沖區中的數據并非清空,只是將兩個指針重置,數據處在一種“被遺忘”狀態,如果進行 get()操作依然可以取出。同時,clear 執行之后的緩沖區無法通過 rewind() 回退指針。
3.3 mark()標記、reset()定位
mark()方法可以記錄當前 position 的位置,并可以通過 reset() 方法恢復到 mark()
3.4?hasRemaining()是否有未讀數據、remaining()獲取未讀數據數量
hasRemaining() 用于判斷讀模式下的 Buffer 中是否還有未讀數據;
remaining() 方法可以返回剩余可操作的元素個數。其值與 limit - position 的差值相等。
3.5 示例程序
從一開始創建一個 Buffer 開始,通過存入、讀取數據來觀察各個屬性:capacity、limit、position、mark 等的變化。
創建:
// 分配 1 KB 大小的緩沖區 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); System.out.println("=============allocate()==========="); System.out.println("position = " + byteBuffer.position()); System.out.println("limit = " + byteBuffer.limit()); System.out.println("capacity = " + byteBuffer.capacity());存數據:
System.out.println("=============put()==========="); String name = "abcde"; byteBuffer.put(name.getBytes()); System.out.println("position = " + byteBuffer.position()); System.out.println("limit = " + byteBuffer.limit()); System.out.println("capacity = " + byteBuffer.capacity());反轉:
System.out.println("============flip()==========="); byteBuffer.flip(); System.out.println("position = " + byteBuffer.position()); System.out.println("limit = " + byteBuffer.limit()); System.out.println("capacity = " + byteBuffer.capacity());讀數據:
System.out.println("============get()==========="); byte[] dst = new byte[byteBuffer.limit()]; byteBuffer.get(dst); System.out.println("position = " + byteBuffer.position()); System.out.println("limit = " + byteBuffer.limit()); System.out.println("capacity = " + byteBuffer.capacity()); System.out.println(new String(dst));倒帶:
System.out.println("============rewind()==========="); byteBuffer.rewind(); System.out.println("position = " + byteBuffer.position()); System.out.println("limit = " + byteBuffer.limit()); System.out.println("capacity = " + byteBuffer.capacity());清空:
System.out.println("============clear()==========="); byteBuffer.clear(); System.out.println("position = " + byteBuffer.position()); System.out.println("limit = " + byteBuffer.limit()); System.out.println("capacity = " + byteBuffer.capacity());標記、定位標記:
ByteBuffer buf = ByteBuffer.allocate(5); buf.put("abcde".getBytes()); buf.flip(); byte[] dst = new byte[buf.limit()]; buf.get(dst, 0, 2); // get(byte[] dst, int offset, int length) System.out.println("第一次取出結果:" + new String(dst)); System.out.println("position:" + buf.position()); // mark()標記 buf.mark(); buf.get(dst, 2, 2); // get(byte[] dst, int offset, int length) System.out.println("第二次取出結果:" + new String(dst)); System.out.println("position:" + buf.position()); // 恢復到mark buf.reset(); System.out.println("reset 恢復到 mark 位置"); System.out.println("position:" + buf.position());查詢剩余數據:
// remaining() 獲取緩沖區中還可以操作的數量 if (buf.hasRemaining()) {System.out.println(buf.remaining());System.out.println("limit - position = " + (buf.limit() - buf.position())); }四、直接緩沖區與非直接緩沖區
字節緩沖區要么是 直接緩沖區,要么是 非直接緩沖區。
非直接緩沖區屬于常規操作,傳統的 IO 流和 allocate() 方法分配的緩沖區都是非直接緩沖區,建立在 JVM 內存中。這種常規的非直接緩沖區會將內核地址空間中的內容拷貝到用戶地址空間(中間緩沖區)后再由程序進行讀或寫操作,換句話說,磁盤上的文件在與應用程序交互的過程中會在兩個緩存中來回進行復制拷貝。
而直接緩沖區絕大多數情況用于顯著提升性能,緩沖區直接建立在物理內存(相對于JVM 的內存空間)中,省去了在兩個存儲空間中來回復制的操作,可以通過調用 ByteBuffer 的 allocateDirect() 工廠方法來創建。直接緩沖區中的內容可以駐留在常規的垃圾回收堆之外,因此它們對應用程序的內存需求量造成的影響可能并不明顯。另外,直接緩沖區還可以通過 FileChannel 的 map() 方法將文件直接映射到內存中來創建,該方法將返回 MappedByteBuffer 。
直接或非直接緩沖區只針對字節緩沖區而言。字節緩沖區是那種類型可以通過 isDirect() 方法來判斷。
注意!!!直接緩沖區性能雖然好,但是緩沖區直接建立在物理內存中,無法由 GC來釋放,可控性差,同時分配和銷毀成本很高!在對性能不是特別依賴的場景不建議使用!
?
總結
以上是生活随笔為你收集整理的Java NIO ———— Buffer 缓冲区详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多媒体计算机技术的主要特点,多媒体技术主
- 下一篇: java美元兑换,(Java实现) 美元