目錄
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
的種類,並對常用方法進行樂簡單的介紹