ByteBuffer前先後後看過好幾回了,實際使用也用了一些,總以爲條理不夠清晰。html
《程序員的思惟修煉》一本書講過,主動學習,要比單純看資料效果來的好,因此乾脆寫個詳細點的文章來記錄一下。java
緩衝區(Buffer)
緩衝區(Buffer)就是在內存中預留指定大小的存儲空間用來對輸入/輸出(I/O)的數據做臨時存儲,這部分預留的內存空間就叫作緩衝區:程序員
使用緩衝區有這麼兩個好處:api
一、減小實際的物理讀寫次數數組
二、緩衝區在建立時就被分配內存,這塊內存區域一直被重用,能夠減小動態分配和回收內存的次數緩存
舉個簡單的例子,好比A地有1w塊磚要搬到B地oracle
因爲沒有工具(緩衝區),咱們一次只能搬一本,那麼就要搬1w次(實際讀寫次數)函數
若是A,B兩地距離很遠的話(IO性能消耗),那麼性能消耗將會很大工具
可是要是此時咱們有輛大卡車(緩衝區),一次可運5000本,那麼2次就夠了post
相比以前,性能確定是大大提升了。
並且通常在實際過程當中,咱們通常是先將文件讀入內存,再從內存寫出到別的地方
這樣在輸入輸出過程當中咱們均可以用緩存來提高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
參考連接: