Java NIO 之 Buffer(緩衝區)

一 Buffer(緩衝區)介紹

Java NIO Buffers用於和NIO Channel交互。 咱們從Channel中讀取數據到buffers裏,從Buffer把數據寫入到Channels.html

Buffer本質上就是一塊內存區,能夠用來寫入數據,並在稍後讀取出來。這塊內存被NIO Buffer包裹起來,對外提供一系列的讀寫方便開發的接口。java

在Java NIO中使用的核心緩衝區以下(覆蓋了經過I/O發送的基本數據類型:byte, char、short, int, long, float, double ,long):程序員

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • FloatBuffer
  • DoubleBuffer
  • LongBuffer

Java NIO中使用的核心緩衝區

利用Buffer讀寫數據,一般遵循四個步驟:

  1. 把數據寫入buffer;
  2. 調用flip;
  3. 從Buffer中讀取數據;
  4. 調用buffer.clear()或者buffer.compact()。

當寫入數據到buffer中時,buffer會記錄已經寫入的數據大小。當須要讀數據時,經過 flip() 方法把buffer從寫模式調整爲讀模式;在讀模式下,能夠讀取全部已經寫入的數據。面試

當讀取完數據後,須要清空buffer,以知足後續寫入操做。清空buffer有兩種方式:調用 clear()compact() 方法。clear會清空整個buffer,compact則只清空已讀取的數據,未被讀取的數據會被移動到buffer的開始位置,寫入位置則近跟着未讀數據以後。api

Buffer的容量,位置,上限(Buffer Capacity, Position and Limit)

Buffer緩衝區實質上就是一塊內存,用於寫入數據,也供後續再次讀取數據。這塊內存被NIO Buffer管理,並提供一系列的方法用於更簡單的操做這塊內存。數組

一個Buffer有三個屬性是必須掌握的,分別是:緩存

  • capacity容量
  • position位置
  • limit限制

position和limit的具體含義取決於當前buffer的模式。capacity在兩種模式下都表示容量。微信

下面有張示例圖,描訴了讀寫模式下position和limit的含義:oracle

不一樣模式下position和limit的含義

容量(Capacity)

做爲一塊內存,buffer有一個固定的大小,叫作capacit(容量)。也就是最多隻能寫入容量值得字節,整形等數據。一旦buffer寫滿了就須要清空已讀數據以便下次繼續寫入新的數據。yii

位置(Position)

當寫入數據到Buffer的時候須要從一個肯定的位置開始,默認初始化時這個位置position爲0,一旦寫入了數據好比一個字節,整形數據,那麼position的值就會指向數據以後的一個單元,position最大能夠到capacity-1.

當從Buffer讀取數據時,也須要從一個肯定的位置開始。buffer從寫入模式變爲讀取模式時,position會歸零,每次讀取後,position向後移動。

上限(Limit)

在寫模式,limit的含義是咱們所能寫入的最大數據量,它等同於buffer的容量。

一旦切換到讀模式,limit則表明咱們所能讀取的最大數據量,他的值等同於寫模式下position的位置。換句話說,您能夠讀取與寫入數量相同的字節數(限制設置爲寫入的字節數,由位置標記)。

二 Buffer的常見方法

方法 介紹
abstract Object array() 返回支持此緩衝區的數組 (可選操做)
abstract int arrayOffset() 返回該緩衝區的緩衝區的第一個元素的背襯數組中的偏移量 (可選操做)
int capacity() 返回此緩衝區的容量
Buffer clear() 清除此緩存區。將position = 0;limit = capacity;mark = -1;
Buffer flip() flip()方法能夠吧Buffer從寫模式切換到讀模式。調用flip方法會把position歸零,並設置limit爲以前的position的值。 也就是說,如今position表明的是讀取位置,limit標示的是已寫入的數據位置。
abstract boolean hasArray() 告訴這個緩衝區是否由可訪問的數組支持
boolean hasRemaining() return position < limit,返回是否還有未讀內容
abstract boolean isDirect() 判斷個緩衝區是否爲 direct
abstract boolean isReadOnly() 判斷告知這個緩衝區是不是隻讀的
int limit() 返回此緩衝區的限制
Buffer position(int newPosition) 設置這個緩衝區的位置
int remaining() return limit - position; 返回limit和position之間相對位置差
Buffer rewind() 把position設爲0,mark設爲-1,不改變limit的值
Buffer mark() 將此緩衝區的標記設置在其位置

三 Buffer的使用方式/方法介紹

分配緩衝區(Allocating a Buffer)

爲了得到緩衝區對象,咱們必須首先分配一個緩衝區。在每一個Buffer類中,allocate()方法用於分配緩衝區。

下面來看看ByteBuffer分配容量爲28字節的例子:

ByteBuffer buf = ByteBuffer.allocate(28);

下面來看看另外一個示例:CharBuffer分配空間大小爲2048個字符。

CharBuffer buf = CharBuffer.allocate(2048);

寫入數據到緩衝區(Writing Data to a Buffer)

寫數據到Buffer有兩種方法:

  • 從Channel中寫數據到Buffer
  • 手動寫數據到Buffer,調用put方法

下面是一個實例,演示從Channel寫數據到Buffer:

int bytesRead = inChannel.read(buf); //read into buffer.

經過put寫數據:

buf.put(127);

put方法有不少不一樣版本,對應不一樣的寫數據方法。例如把數據寫到特定的位置,或者把一個字節數據寫入buffer。看考JavaDoc文檔能夠查閱的更多數據。

翻轉(flip())

flip()方法能夠吧Buffer從寫模式切換到讀模式。調用flip方法會把position歸零,並設置limit爲以前的position的值。 也就是說,如今position表明的是讀取位置,limit標示的是已寫入的數據位置。

從Buffer讀取數據(Reading Data from a Buffer)

從Buffer讀數據也有兩種方式。

  • 從buffer讀數據到channel
  • 從buffer直接讀取數據,調用get方法

讀取數據到channel的例子:

int bytesWritten = inChannel.write(buf);

調用get讀取數據的例子:

byte aByte = buf.get();

get也有諸多版本,對應了不一樣的讀取方式。

rewind()

Buffer.rewind()方法將position置爲0,這樣咱們能夠重複讀取buffer中的數據。limit保持不變。

clear() and compact()

一旦咱們從buffer中讀取完數據,須要複用buffer爲下次寫數據作準備。只須要調用clear()或compact()方法。

若是調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數據並未清除,只是這些標記告訴咱們能夠從哪裏開始往Buffer裏寫數據。

若是Buffer還有一些數據沒有讀取完,調用clear就會致使這部分數據被「遺忘」,由於咱們沒有標記這部分數據未讀。

針對這種狀況,若是須要保留未讀數據,那麼可使用compact。 所以 compact()clear() 的區別就在於: 對未讀數據的處理,是保留這部分數據仍是一塊兒清空

mark()與reset()方法

經過調用Buffer.mark()方法,能夠標記Buffer中的一個特定position。以後能夠經過調用Buffer.reset()方法恢復到這個position。例如:

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset();  //set position back to mark.

equals() and compareTo()

能夠用eqauls和compareTo比較兩個buffer

equals():

判斷兩個buffer相對,需知足:

  • 類型相同
  • buffer中剩餘字節數相同
  • 全部剩餘字節相等

從上面的三個條件能夠看出,equals只比較buffer中的部份內容,並不會去比較每個元素。

compareTo():

compareTo也是比較buffer中的剩餘元素,只不過這個方法適用於比較排序的:

四 Buffer經常使用方法測試

這裏以ByteBuffer爲例子說明抽象類Buffer的實現類的一些常見方法的使用:

package channel;

import java.nio.ByteBuffer;

public class ByteBufferMethods {
    public static void main(String args[]){
        //分配緩衝區(Allocating a Buffer)
        ByteBuffer buffer = ByteBuffer.allocate(33);

        System.out.println("-------------Test reset-------------");
        //clear()方法,position將被設回0,limit被設置成 capacity的值
        buffer.clear();
       // 設置這個緩衝區的位置
        buffer.position(5);
        //將此緩衝區的標記設置在其位置。沒有buffer.mark();這句話會報錯
        buffer.mark();
        buffer.position(10);
        System.out.println("before reset:      " + buffer);
        //將此緩衝區的位置重置爲先前標記的位置。(buffer.position(5))
        buffer.reset();
        System.out.println("after reset:       " + buffer);

        System.out.println("-------------Test rewind-------------");
        buffer.clear();
        buffer.position(10);
        //返回此緩衝區的限制。
        buffer.limit(15);
        System.out.println("before rewind:       " + buffer);
        //把position設爲0,mark設爲-1,不改變limit的值
        buffer.rewind();
        System.out.println("before rewind:       " + buffer);

        System.out.println("-------------Test compact-------------");
        buffer.clear();
        buffer.put("abcd".getBytes());
        System.out.println("before compact:       " + buffer);
        System.out.println(new String(buffer.array()));
        //limit = position;position = 0;mark = -1; 翻轉,也就是讓flip以後的position到limit這塊區域變成以前的0到position這塊,
        //翻轉就是將一個處於存數據狀態的緩衝區變爲一個處於準備取數據的狀態
        buffer.flip();
        System.out.println("after flip:       " + buffer);
        //get()方法:相對讀,從position位置讀取一個byte,並將position+1,爲下次讀寫做準備
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println("after three gets:       " + buffer);
        System.out.println("\t" + new String(buffer.array()));
        //把從position到limit中的內容移到0到limit-position的區域內,position和limit的取值也分別變成limit-position、capacity。
        // 若是先將positon設置到limit,再compact,那麼至關於clear()
        buffer.compact();
        System.out.println("after compact:       " + buffer);
        System.out.println("\t" + new String(buffer.array()));

        System.out.println("-------------Test get-------------");
        buffer = ByteBuffer.allocate(32);
        buffer.put((byte) 'a').put((byte) 'b').put((byte) 'c').put((byte) 'd')
                .put((byte) 'e').put((byte) 'f');
        System.out.println("before flip():       " + buffer);
        // 轉換爲讀取模式
        buffer.flip();
        System.out.println("before get():       " + buffer);
        System.out.println((char) buffer.get());
        System.out.println("after get():       " + buffer);
        // get(index)不影響position的值
        System.out.println((char) buffer.get(2));
        System.out.println("after get(index):       " + buffer);
        byte[] dst = new byte[10];
        buffer.get(dst, 0, 2);
        System.out.println("after get(dst, 0, 2):       " + buffer);
        System.out.println("\t dst:" + new String(dst));
        System.out.println("buffer now is:       " + buffer);
        System.out.println("\t" + new String(buffer.array()));

        System.out.println("-------------Test put-------------");
        ByteBuffer bb = ByteBuffer.allocate(32);
        System.out.println("before put(byte):       " + bb);
        System.out.println("after put(byte):       " + bb.put((byte) 'z'));
        System.out.println("\t" + bb.put(2, (byte) 'c'));
        // put(2,(byte) 'c')不改變position的位置
        System.out.println("after put(2,(byte) 'c'):       " + bb);
        System.out.println("\t" + new String(bb.array()));
        // 這裏的buffer是 abcdef[pos=3 lim=6 cap=32]
        bb.put(buffer);
        System.out.println("after put(buffer):       " + bb);
        System.out.println("\t" + new String(bb.array()));
    }
}

參考:

官方JDK相關文檔

谷歌搜索排名第一的Java NIO教程

《Java程序員修煉之道》

ByteBuffer經常使用方法詳解

Java NIO 易百教程

歡迎關注個人微信公衆號:"Java面試通關手冊"(一個有溫度的微信公衆號,期待與你共同進步~~~堅持原創,分享美文,分享各類Java學習資源):

相關文章
相關標籤/搜索