深刻理解ByteBuffer

ByteBuffer類是在Java NIO中經常使用的一個緩衝區類,使用它能夠進行高效的IO操做,可是,若是對經常使用方法的理解有錯誤,那麼就會出現意想不到的bug。數組

ByteBuffer類的經常使用方法
先來看看一個基本的程序緩存

public void test() throws IOException
    {
        ByteBuffer buff = ByteBuffer.allocate(128);
        FileChannel fin = null;
        FileChannel fout = null;
        try
        {
            fin = new FileInputStream("filein").getChannel();
            fout = new FileOutputStream("fileout").getChannel();
            while(fin.read(buff) != -1) {
                buff.flip();
                fout.write(buff);
                buff.clear();
            }
        }
        catch (FileNotFoundException e)
        {
 
        } finally {
            try {
                if(fin != null) {
                    fin.close();
                }
                if(fout != null) {
                    fout.close();
                }
            } catch(IOException e) {
                throw e;
            }
        }
    }
在test方法中,首先經過ByteBuffer.allocate()方法分配了一段內存空間,做爲緩存,allocate方法對緩存自動清零,而後打開一個輸入文件管道fin和一個輸出文件管道fout,在循環中先從fin讀出數據存放到buff緩衝區中,再將buff緩衝中的內容寫入fout。固然這對於先從文件中讀,而後再寫這樣的場景,這不是高效的作法。 
能夠看到先從fin中讀出數據後,首先要調用ByteBuffer.flip()方法,若將數據寫入輸出文件後,還要調用ByteBuffer.clear()方法,爲何要這樣作呢?函數

ByteBuffer能夠做爲一個緩衝區,是由於它是內存中的一段連續的空間,在ByteBuffer對象內部定義了四個索引,分別是mark,position,limit,capacity,其中this

mark用於對當前position的標記指針

position表示當前可讀寫的指針,若是是向ByteBuffer對象中寫入一個字節,那麼就會向position所指向的地址寫入這個字節,若是是從ByteBuffer讀出一個字節,那麼就會讀出position所指向的地址讀出這個字節,讀寫完成後,position加1對象

limit是能夠讀寫的邊界,當position到達limit時,就表示將ByteBuffer中的內容讀完,或者將ByteBuffer寫滿了。繼承

capacity是這個ByteBuffer的容量,上面的程序中調用ByteBuffer.allocate(128)就表示建立了一個容量爲capacity字節的ByteBuffer對象。索引

瞭解了這四個變量以後,再來看看前面的程序。之因此調用ByteBuffer.flip()方法是由於在向ByteBuffer寫入數據後,position爲緩衝區中剛剛讀入的數據的最後一個字節的位置,flip方法將limit值置爲position值,position置0,這樣在調用get*()方法從ByteBuffer中取數據時就能夠取到ByteBuffer中的有效數據,JDK中flip方法的代碼以下:接口

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}
在調用four.write(buff)時,就將buff緩衝區中的數據寫入到輸出管道,此時調用ByteBuffer.clear()方法爲下次從管道中讀取數據作準備,可是調用clear方法並不將緩衝區的數據清空,而是設置position,mark,limit這三個變量的值,JDK中clear方法的代碼以下:ip

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}
這個方法命名給人的感受就是將數據清空了,可是實際上卻不是的,它並無清空緩衝區中的數據,而至重置了對象中的三個索引值,若是不清空的話,假設這次該ByteBuffer中的數據是滿的,下次讀取的數據不足以填滿緩衝區,那麼就會存在上一次已經處理的的數據,因此在判斷緩衝區中是否還有可用數據時,使用ByteBuffer.hasRemaining()方法,在JDK中,這個方法的代碼以下:

public final boolean hasRemaining() {
    return position < limit;
}
在該方法中,比較了position和limit的值,用以判斷是否還有可用數據。

在ByteBuffer類中,還有個方法是compact,對於ByteBuffer,其子類HeapByteBuffer的compact方法實現是這樣的:

public ByteBuffer compact() {
    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
    position(remaining());
    limit(capacity());
    return this;
}
若是position()方法返回當前緩衝區中的position值,remaining()方法返回limit與position這段區間的長度,JDK中的remaining()方法代碼以下

public final int remaining() {
    return limit - position;
}
因此compact()方法中第一條語句做用是將數組hb當前position所指向的位置開始複製長度爲limit-position的數據到hb數組的開始出,其中使用到了ix()函數,這個函數是將參數值加上一個offset值,offset即一個偏移值,在這樣的好比一個這樣的場景對於一個很大的緩衝區,將其分紅兩段,第一段的起始位置是p1,長度是q1,第二段起始位置是p2,長度是q2,那麼能夠分別將這兩段包裝成一個HeapByteBuffer對象,而後這兩個HeapByteBuffer對象(ByteBuffer的子類,默認實現)的offset屬性分別設置爲p1和p2,這樣就能夠經過在內部使用ix()函數來簡化ByteBuffer對外提供的接口,在使用者看來,與默認的ByteBuffer並無區別。

在compact函數中,接着將當前的緩衝區的position索引置爲limit-position,limit索引置爲緩衝區的容量,這樣調用compact方法中就能夠將緩衝區的有效數據所有移到緩衝區的首部,而position指向下一個可寫位置。

好比剛剛建立一個ByteBuffer對象buff時,position=0,limit=capacity,那麼此時調用buff.hasRemaining()則會返回true,這樣來判斷緩衝區中是否有數據是不行的,由於此時緩衝區中的存儲的所有是0,可是調用一次compact()方法就能夠將position置爲limit值,這樣再經過buff.hasRemaining()就會返回false,能夠與後面的邏輯一塊兒處理了。

ByteBuffer還有一個名爲mark的方法,該方法設置mark索引爲position的值,JDK中的代碼以下:

public final Buffer mark() {
    mark = position;
    return this;
}
與其功能相反的方法爲reset方法,即將position的值設置爲mark,JDK中的代碼以下:

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}
此外還有一個名爲rewind的方法,這個方法將position索引置爲0,mark索引置爲-1,JDK中的代碼以下:

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}
經過這些方法,就能夠很方便的操做一個緩衝區,關鍵是要理解這些方法具體的做用,以及對三個索引值的影響(capacity是不變的)。

ByteBuffer繼承自Buffer類,上面的方法四個索引值都定義在Buffer類中,操做索引值的方法也都定義在Buffer類中。

相關文章
相關標籤/搜索