Java NIO之Buffer的使用

目錄

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

Buffer簡介

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

根據數據類型的不一樣,Java爲除了boolean類型以外的其他7種基本類型提供了相應類型的緩衝區,
分別是ByteBufferCharBufferShortBufferIntBufferLongBuffer
FloatBufferDoubleBuffer。他們都繼承自抽象類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屬性,對應的描述以下app

屬性 描述
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的本質是一個數組,建立時須要指定數組的大小this

Buffer的使用

Buffer的使用通常分爲四個步驟spa

  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從寫模式切換到讀模式。code

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

相關文章
相關標籤/搜索