ByteBuffer前前后后看過好幾次了,實際使用也用了一些,總覺得條理不夠清晰。
《程序員的思維修煉》一本書講過,主動學習,要比單純看資料效果來的好,所以干脆寫個詳細點的文章來記錄一下。
緩沖區(Buffer)
緩沖區(Buffer)就是在內存中預留指定大小的存儲空間用來對輸入/輸出(I/O)的數據作臨時存儲,這部分預留的內存空間就叫做緩沖區:
使用緩沖區有這么兩個好處:
1、減少實際的物理讀寫次數
2、緩沖區在創建時就被分配內存,這塊內存區域一直被重用,可以減少動態分配和回收內存的次數
舉個簡單的例子,比如A地有1w塊磚要搬到B地
由於沒有工具(緩沖區),我們一次只能搬一本,那么就要搬1w次(實際讀寫次數)
如果A,B兩地距離很遠的話(IO性能消耗),那么性能消耗將會很大
但是要是此時我們有輛大卡車(緩沖區),一次可運5000本,那么2次就夠了
相比之前,性能肯定是大大提高了。
而且一般在實際過程中,我們一般是先將文件讀入內存,再從內存寫出到別的地方
這樣在輸入輸出過程中我們都可以用緩存來提升IO性能。
所以,buffer在IO中很重要。在舊I/O類庫中(相對java.nio包)中的BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter在其實現中都運用了緩沖區。java.nio包公開了Buffer API,使得Java程序可以直接控制和運用緩沖區。
在Java NIO中,緩沖區的作用也是用來臨時存儲數據,可以理解為是I/O操作中數據的中轉站。緩沖區直接為通道(Channel)服務,寫入數據到通道或從通道讀取數據,這樣的操利用緩沖區數據來傳遞就可以達到對數據高效處理的目的。
概述
ByteBuffer是NIO里用得最多的Buffer,它包含兩個實現方式:HeapByteBuffer
是基於Java堆的實現,而DirectByteBuffer
則使用了unsafe
的API進行了堆外的實現。這里只說HeapByteBuffer。
使用
ByteBuffer最核心的方法是put(byte)
和get()
。分別是往ByteBuffer里寫一個字節,和讀一個字節。
值得注意的是,ByteBuffer的讀寫模式是分開的,正常的應用場景是:往ByteBuffer里寫一些數據,然后flip(),然后再讀出來。
這里插兩個Channel方面的對象,以便更好的理解Buffer。
ReadableByteChannel
是一個從Channel中讀取數據,並保存到ByteBuffer的接口,它包含一個方法:
public
int
read(ByteBuffer dst)
throws
IOException;
WritableByteChannel
則是從ByteBuffer中讀取數據,並輸出到Channel的接口:
public
int
write(ByteBuffer src)
throws
IOException;
那么,一個ByteBuffer的使用過程是這樣的:
1. byteBuffer = ByteBuffer.allocate(N); //創建
2. readableByteChannel.read(byteBuffer); //讀取數據,寫入byteBuffer
3. byteBuffer.flip(); //變讀為寫
4. writableByteChannel.write(byteBuffer); //讀取byteBuffer,寫入數據
看到這里,一般都不太明白flip()干了什么事,先從ByteBuffer結構說起:
ByteBuffer的創建和讀寫
1. ByteBuffer定義了4個static方法來做創建工作:
ByteBuffer allocate(int capacity) //創建一個指定capacity的ByteBuffer。
ByteBuffer allocateDirect(int capacity) //創建一個direct的ByteBuffer,這樣的ByteBuffer在參與IO操作時性能會更好
ByteBuffer wrap(byte [] array)
ByteBuffer wrap(byte [] array, int offset, int length) //把一個byte數組或byte數組的一部分包裝成ByteBuffer。
2. ByteBuffer定義了一系列get和put操作來從中讀寫byte數據,如下面幾個:
byte get()
ByteBuffer get(byte [] dst)
byte get(int index)
ByteBuffer put(byte b)
ByteBuffer put(byte [] src)
ByteBuffer put(int index, byte b)
這些操作可分為絕對定位和相對定為兩種,相對定位的讀寫操作依靠position來定位Buffer中的位置,並在操
作完成后會更新position的值。在其它類型的buffer中,也定義了相同的函數來讀寫數據,唯一不同的就是一
些參數和返回值的類型。
3. 除了讀寫byte類型數據的函數,ByteBuffer的一個特別之處是它還定義了讀寫其它primitive數據的方法,如:
int getInt() //從ByteBuffer中讀出一個int值。
ByteBuffer putInt(int value) // 寫入一個int值到ByteBuffer中。
3.1 字節序
讀寫其它類型的數據牽涉到字節序問題,ByteBuffer會按其字節序(大字節序或小字節序)寫入或讀出一個其它
類型的數據(int,long…)。字節序可以用order方法來取得和設置:
ByteOrder order() //返回ByteBuffer的字節序。
ByteBuffer order(ByteOrder bo) // 設置ByteBuffer的字節序。
3.2 ByteOrder
用來表示ByteBuffer字節序的類,可將其看成java中的enum類型。主要定義了下面幾個static方法和屬性:
ByteOrder BIG_ENDIAN 代表大字節序的ByteOrder。
ByteOrder LITTLE_ENDIAN 代表小字節序的ByteOrder。
ByteOrder nativeOrder() 返回當前硬件平台的字節序。
4. ByteBuffer另一個特別的地方是可以在它的基礎上得到其它類型的buffer。如:
CharBuffer asCharBuffer()
為當前的ByteBuffer創建一個CharBuffer的視圖。在該視圖buffer中的讀寫操作會按照ByteBuffer的字節
序作用到ByteBuffer中的數據上。
用這類方法創建出來的buffer會從ByteBuffer的position位置開始到limit位置結束,可以看作是這段數據
的視圖。視圖buffer的readOnly屬性和direct屬性與ByteBuffer的一致,而且也只有通過這種方法,才可
以得到其他數據類型的direct buffer。
ByteBuffer內部字段
byte[] buff
buff即內部用於緩存的數組。
position
當前讀取的位置。
讀/寫操作的當前下標。當使用buffer的相對位置進行讀/寫操作時,讀/寫會從這個下標進行,並在操作完成后,
buffer會更新下標的值。
mark
為某一讀過的位置做標記,便於某些時候回退到該位置。
一個臨時存放的位置下標。調用mark()會將mark設為當前的position的值,以后調用reset()會將position屬性設
置為mark的值。mark的值總是小於等於position的值,如果將position的值設的比mark小,當前的mark值會被拋棄掉。
capacity
初始化時候的容量。
這個Buffer最多能放多少數據。capacity一般在buffer被創建的時候指定。
limit
在Buffer上進行的讀寫操作都不能越過這個下標。當寫數據到buffer中時,limit一般和capacity相等,當讀數據時,
limit代表buffer中有效數據的長度。
讀寫的上限,limit<=capacity。
這些屬性總是滿足以下條件:
0 <= mark <= position <= limit <= capacity
limit和position的值除了通過limit()和position()函數來設置,也可以通過下面這些函數來改變:
Buffer clear()
把position設為0,把limit設為capacity,一般在把數據寫入Buffer前調用。
Buffer flip()
把limit設為當前position,把position設為0,一般在從Buffer讀出數據前調用。
Buffer rewind()
把position設為0,limit不變,一般在把數據重寫入Buffer前調用。
compact()
該方法的作用是將 position 與 limit之間的數據復制到buffer的開始位置,復制后 position = limit -position,limit = capacity
但如果position 與limit 之間沒有數據的話發,就不會進行復制 詳細參考:java nio Buffer 中 compact的作用
mark()與reset()方法
通過調用Buffer.mark()方法,可以標記Buffer中的一個特定position。之后可以通過調用Buffer.reset()方法恢復到這個position。例如:
1.buffer.mark();
2.//call buffer.get() a couple of times, e.g. during parsing.
3.buffer.reset(); //set position back to mark
equals()與compareTo()方法
可以使用equals()和compareTo()方法兩個Buffer。
equals()
當滿足下列條件時,表示兩個Buffer相等:
- 有相同的類型(byte、char、int等)。
- Buffer中剩余的byte、char等的個數相等。
- Buffer中所有剩余的byte、char等都相同。
如你所見,equals只是比較Buffer的一部分,不是每一個在它里面的元素都比較。實際上,它只比較Buffer中的剩余元素。
compareTo()方法
compareTo()方法比較兩個Buffer的剩余元素(byte、char等), 如果滿足下列條件,則認為一個Buffer“小於”另一個Buffer:
-
- 第一個不相等的元素小於另一個Buffer中對應的元素 。
- 所有元素都相等,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數比另一個少)。
Buffer對象有可能是只讀的,這時,任何對該對象的寫操作都會觸發一個ReadOnlyBufferException。
isReadOnly()方法可以用來判斷一個Buffer是否只讀。
圖解
put
寫模式下,往buffer里寫一個字節,並把postion移動一位。寫模式下,一般limit與capacity相等。
flip
寫完數據,需要開始讀的時候,將postion復位到0,並將limit設為當前postion。
get
從buffer里讀一個字節,並把postion移動一位。上限是limit,即寫入數據的最后位置。
clear
將position置為0,並不清除buffer內容。
mark相關的方法主要是mark()
(標記)和reset()
(回到標記).
這篇文章對buffer的講解也很詳細,可以參考 Java NIO系列教程(三) Buffer
其他具體的接口信息查閱 http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html
參考鏈接: