目錄
Buffer
簡介Buffer
的核心屬性Buffer
的創建與使用(ByteBuffer
為例)- 總結
- 參考資料
Buffer簡介
緩沖區(Buffer
):本質上是一個數組,用於臨時保存、寫入以及讀取數據。在Java NIO
中,該內存塊包含在NIO Buffer
對象當中,NIO Buffer
對象還提供了一組接口來訪問該內存塊。
根據數據類型的不同,Java
為除了boolean
類型之外的其余7種基本類型提供了相應類型的緩沖區,分別是ByteBuffer
、CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
、DoubleBuffer
。他們都繼承自抽象類Buffer
類,他們的管理方式也都幾乎一樣。UML
類圖如下:
Buffer的核心屬性
BUffer
類的部分實現如下:
public abstract class Buffer {
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
//構造方法
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
/**
* Returns this buffer's capacity.
*
* @return The capacity of this buffer
*/
//返回這個Buffer的容量
public final int capacity() {
return capacity;
}
/**
* Returns this buffer's position.
*
* @return The position of this buffer
*/
//返回這個Buffer中當前的位置(當前操作數)
public final int position() {
return position;
}
/**
* Returns this buffer's limit.
*
* @return The limit of this buffer
*/
//返回當前Buffer中可以被操作的元素的個數
public final int limit() {
return limit;
}
/**
* Sets this buffer's mark at its position.
*
* @return This buffer
*/
//記錄當前position的位置
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
}
其中定義了四個Buffer
屬性,對應的描述如下
屬性 | 描述 |
---|---|
capacity | 容量;用於描述這個Buffer大小,即創建的數組的長度,一旦聲明不可以被改變 |
position | 位置,表示當前緩沖區中正在操作的數據的位置,在切換讀取時會將其置0 |
limit | 界限、限制;表示當前緩沖區中可以操作的數據的大小,默認情況下為Buffer的大小,切換為讀取模式后為數組中元素的個數(准確的說時切換之前position的值) |
mark | 標記;用於記錄當前position的位置,后續操作過程中可以使用reset()方法將position還原至最后一次mark的位置 |
Buffer的創建與使用(ByteBuffer為例)
Buffer的創建
在Java NIO
中可以使用對應Buffer
類的allocate()
或者allocateDirect()
靜態方法創建。
//使用allocate()創建
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
//使用allocateDirect()創建
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
而Buffer
的本質是一個數組,創建時需要指定數組的大小
Buffer的使用
Buffer
的使用一般分為四個步驟
- 向
Buffer
中寫入數據 - 將
Buffer
切換為讀取模式 - 讀取
Buffer
- 將
Buffer
清空,供后續寫入使用
1. 寫如數據
//使用put()方法向Buffer中寫入數據
byteBuffer.put("bmilk".getBytes());
//使用Channel#read()向Buffer中寫入數據
channel.read(byteBuffer);
2. 將Buffer
切換為讀取模式
可以通過調用flip()
方法將Buffer
從寫模式切換到讀模式。
byteBuffer.flip()
調用flip()
方法會將position
設回0,並將limit
設置成之前position
的值。即,現在使用position
標記讀的位置,limit
表示之前寫進了多少個byte
,也就是現在能讀取多少個byte
等。
3. 讀取Buffer
讀取Buffer
有兩種方式:
- 從
Buffer
種讀取數據到Channel
- 使用
get()
方法從Buffer
種讀取數據
//從Buffe中將數據寫入通道
inChannel.write(byteBuffer)
//使用get()方法從BUffer中讀取數據
byte[] bytes=new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
4. 將Buffer
清空,供后續寫入使用
使用clear()
清空緩沖區,清空緩沖區只是使各個指針恢復初始位置,更具體的說是position
設置為0,limit
設置為容量的初始大小。並不會真實清空其中數據,但是可以通過后續的寫覆蓋之前的數據
byteBuffer.clear()
其他的一些方法
- 使用
rewind()
從Buffer
重復讀取數據
//使用`rewind()`從`Buffer`重復讀取數據
//Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有數據。
//limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
Buffer rewind = byteBuffer.rewind();
compact()
方法
clear()
會使使各個指針恢復初始位置,但是實際中可能存在部分數據還沒有被使用,而后續需要使用。又必須清理一部分Buffer
的空間,compact()
方法會將所有未讀數據拷貝到Buffer的起始處,然后將position
指針設置到最后一個未讀元素的后面,現在Buffer
可以進行寫數據,但是不會覆蓋前面的未讀的數據。
mark()
方法與reset()
方法
通過調用Buffer.mark()方法,可以標記Buffer中的當前的position。之后可以通過調用Buffer.reset()方法恢復到這個position。
//使用mark標記當前的position位置
byteBUffer.mark()
//使用reset方法使position指針返回這個位置
byteBuffer.reset()
4.equals()
方法與compareTo()
方法
當需要比較兩個Buffer
時可以使用equals()
方法與compareTo()
方法。
equals()
方法判斷兩個方式是否相等,當滿足下列條件時,表示兩個Buffer
相等
- 有相同的類型(
byte
、char
、int
等)Buffer
中剩余的byte
、char
等的個數相等。- \(\color{#FF3030}{`Buffer`中所有剩余的`byte`、`char`等都相同}\)
compareTo()
方法比較兩個兩個Buffer
的大小,僅比較剩余元素(byte
、char
等)
如果滿足下列條件,則認為一個Buffer
“小於”另一個Buffer
:
- 第一個不相等的元素小於另一個Buffer中對應的元素
- 所有元素都相等,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數比另一個少)。
直接緩沖區與非直接緩沖區
- 非直接緩沖區:通過
allocate()
方法分配緩沖區,將緩沖區建立在JVM內存中 - 直接俄緩沖區:通過
allocateDirect()
方法分配直接緩沖區,將緩沖區建立在物理內存中,可以在某些情況下提高效率
非直接緩沖區
- 非直接緩沖區數據流向圖
直接緩沖區
- 直接緩沖區數據流向圖
直接緩沖區(物理內存映射文件):相比非直接緩沖區省略了copy
的過程,所以說直接緩區可以一定程度上提高效率
弊端:
- 開辟空間時資源消耗大
- 不安全,
java
程序將數據寫入物理內存映射文件中,之后數據將不受Java
程序控制,
什么時候寫入硬盤無法控制(由操作系統控制),當垃圾回收機制釋放引用后才能斷開與之的連接
小結
- 緩沖區要么是直接的,要么是非直接的如果為直接字節緩沖區,則
java
虛擬機會見最大努力直接在此緩沖區上執行本機I/O
。也就是說,每次調用基礎操作系統的I/O
之前或之后,虛擬機都回盡量避免將緩沖區的內容復制到中間緩沖區或者從中間緩沖區中復制內容。 - 直接字節緩沖區可以通過調用此類的
allocateDirect()
工廠方法來創建,此方法返回的緩沖區進行分配和取消分配所需的程本通常高於非直接緩沖區,直接緩沖區的內容可以駐留在常規的垃圾回收堆之外,因此他們對應用程序內存需求造成的影響可能並不明顯,所以建議直接緩沖區主要分配給易受基礎系統的本機I/O操作影響的大型、持久得緩沖區。一般情況下,最好盡在直接緩沖區能在程序性能方面帶來明顯好處時分配他們。 - 直接字節緩沖區還可以通過
FileChannel
的map()
方法,將文件區域直接映射到內存中來創建,該方法返回MappedByteBuffer
。Java
的實現有助於JNI
從本地及代碼創建直接字節緩沖區,如果以上這些緩沖區中的某個緩沖區實例指的是不可訪問的內存區域。則試圖訪問該區域不會更改緩沖區的內容,並且將會在訪問期間或稍后的時間導致拋出不確定的異常 - 字節緩沖區是直接緩沖區還是非直接緩沖區可以通過調用其
isDirect()
方法來確定,提供此方法是為了能夠在性能關鍵型代碼中執行顯式緩沖區管理。
總結
本文簡單介紹了Buffer
的種類,並對常用方法進行樂簡單的介紹