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