Java NIO之Buffer的使用


目錄

  • Buffer簡介
  • Buffer的核心屬性
  • Buffer的創建與使用(ByteBuffer為例)
  • 總結
  • 參考資料

Buffer簡介

緩沖區(Buffer):本質上是一個數組,用於臨時保存、寫入以及讀取數據。在Java NIO中,該內存塊包含在NIO Buffer對象當中,NIO Buffer對象還提供了一組接口來訪問該內存塊。

根據數據類型的不同,Java為除了boolean類型之外的其余7種基本類型提供了相應類型的緩沖區,分別是ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer。他們都繼承自抽象類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的使用一般分為四個步驟

  1. Buffer中寫入數據
  2. Buffer切換為讀取模式
  3. 讀取Buffer
  4. 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有兩種方式:

  1. Buffer種讀取數據到Channel
  2. 使用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()

其他的一些方法

  1. 使用rewind()Buffer重復讀取數據
//使用`rewind()`從`Buffer`重復讀取數據
//Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有數據。
//limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
Buffer rewind = byteBuffer.rewind();
  1. compact()方法

clear()會使使各個指針恢復初始位置,但是實際中可能存在部分數據還沒有被使用,而后續需要使用。又必須清理一部分Buffer的空間,compact()方法會將所有未讀數據拷貝到Buffer的起始處,然后將position指針設置到最后一個未讀元素的后面,現在Buffer可以進行寫數據,但是不會覆蓋前面的未讀的數據。

  1. 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相等

  • 有相同的類型(bytecharint等)
  • Buffer中剩余的bytechar等的個數相等。
  • \(\color{#FF3030}{`Buffer`中所有剩余的`byte`、`char`等都相同}\)

compareTo()方法比較兩個兩個Buffer的大小,僅比較剩余元素(bytechar等)
如果滿足下列條件,則認為一個Buffer“小於”另一個Buffer

  • 第一個不相等的元素小於另一個Buffer中對應的元素
  • 所有元素都相等,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數比另一個少)。

直接緩沖區與非直接緩沖區

  • 非直接緩沖區:通過allocate()方法分配緩沖區,將緩沖區建立在JVM內存中
  • 直接俄緩沖區:通過allocateDirect()方法分配直接緩沖區,將緩沖區建立在物理內存中,可以在某些情況下提高效率

非直接緩沖區

  • 非直接緩沖區數據流向圖

直接緩沖區

  • 直接緩沖區數據流向圖

直接緩沖區(物理內存映射文件):相比非直接緩沖區省略了copy的過程,所以說直接緩區可以一定程度上提高效率

弊端:

  • 開辟空間時資源消耗大
  • 不安全,java程序將數據寫入物理內存映射文件中,之后數據將不受Java程序控制,
    什么時候寫入硬盤無法控制(由操作系統控制),當垃圾回收機制釋放引用后才能斷開與之的連接

小結

  • 緩沖區要么是直接的,要么是非直接的如果為直接字節緩沖區,則java虛擬機會見最大努力直接在此緩沖區上執行本機I/O。也就是說,每次調用基礎操作系統的I/O之前或之后,虛擬機都回盡量避免將緩沖區的內容復制到中間緩沖區或者從中間緩沖區中復制內容。
  • 直接字節緩沖區可以通過調用此類的allocateDirect()工廠方法來創建,此方法返回的緩沖區進行分配和取消分配所需的程本通常高於非直接緩沖區,直接緩沖區的內容可以駐留在常規的垃圾回收堆之外,因此他們對應用程序內存需求造成的影響可能並不明顯,所以建議直接緩沖區主要分配給易受基礎系統的本機I/O操作影響的大型、持久得緩沖區。一般情況下,最好盡在直接緩沖區能在程序性能方面帶來明顯好處時分配他們。
  • 直接字節緩沖區還可以通過FileChannelmap()方法,將文件區域直接映射到內存中來創建,該方法返回MappedByteBufferJava的實現有助於JNI從本地及代碼創建直接字節緩沖區,如果以上這些緩沖區中的某個緩沖區實例指的是不可訪問的內存區域。則試圖訪問該區域不會更改緩沖區的內容,並且將會在訪問期間或稍后的時間導致拋出不確定的異常
  • 字節緩沖區是直接緩沖區還是非直接緩沖區可以通過調用其isDirect()方法來確定,提供此方法是為了能夠在性能關鍵型代碼中執行顯式緩沖區管理。

總結

本文簡單介紹了Buffer的種類,並對常用方法進行樂簡單的介紹

參考資料

Java NIO系列教程(三) Buffer


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM