深入理解ByteBuffer(轉)


轉:http://blog.csdn.net/workformywork/article/details/26699345?utm_source=tuicool&utm_medium=referral

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,其中

  • 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方法的代碼如下:

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類中。

總結

通過對ByteBuffer中的四個索引值操作方法的分析,加深了對ByteBuffer的理解。理解ByteBuffer和其他幾種Buffer的關鍵是要理解在使用中各個方法是如何操作索引值的,特別要注意的是clear方法並沒有清除緩沖區的內容。


免責聲明!

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



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