緩沖區(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)服務,寫入數據到通道或從通道讀取數據,這樣的操利用緩沖區數據來傳遞就可以達到對數據高效處理的目的。在NIO中主要有八種緩沖區類(其中MappedByteBuffer是專門用於內存映射的一種ByteBuffer):

Fields
所有緩沖區都有4個屬性:capacity、limit、position、mark,並遵循:mark <= position <= limit <= capacity,下表格是對着4個屬性的解釋:
屬性 描述
| Capacity | 容量,即可以容納的最大數據量;在緩沖區創建時被設定並且不能改變 |
| Limit | 表示緩沖區的當前終點,不能對緩沖區超過極限的位置進行讀寫操作。且極限是可以修改的 |
| Position | 位置,下一個要被讀或寫的元素的索引,每次讀寫緩沖區數據時都會改變改值,為下次讀寫作准備 |
| Mark | 標記,調用mark()來設置mark=position,再調用reset()可以讓position恢復到標記的位置 |
Methods
1、實例化
java.nio.Buffer類是一個抽象類,不能被實例化。Buffer類的直接子類,如ByteBuffer等也是抽象類,所以也不能被實例化。
但是ByteBuffer類提供了4個靜態工廠方法來獲得ByteBuffer的實例:
方法 描述
| allocate(int capacity) | 從堆空間中分配一個容量大小為capacity的byte數組作為緩沖區的byte數據存儲器 |
| allocateDirect(int capacity) | 是不使用JVM堆棧而是通過操作系統來創建內存塊用作緩沖區,它與當前操作系統能夠更好的耦合,因此能進一步提高I/O操作速度。但是分配直接緩沖區的系統開銷很大,因此只有在緩沖區較大並長期存在,或者需要經常重用時,才使用這種緩沖區 |
| wrap(byte[] array) | 這個緩沖區的數據會存放在byte數組中,bytes數組或buff緩沖區任何一方中數據的改動都會影響另一方。其實ByteBuffer底層本來就有一個bytes數組負責來保存buffer緩沖區中的數據,通過allocate方法系統會幫你構造一個byte數組 |
| wrap(byte[] array, int offset, int length) |
在上一個方法的基礎上可以指定偏移量和長度,這個offset也就是包裝后byteBuffer的position,而length呢就是limit-position的大小,從而我們可以得到limit的位置為length+position(offset) |
我寫了這幾個方法的測試方法,大家可以運行起來更容易理解
2、另外一些常用的方法
方法 描述
| limit(), limit(10)等 | 其中讀取和設置這4個屬性的方法的命名和jQuery中的val(),val(10)類似,一個負責get,一個負責set |
| reset() | 把position設置成mark的值,相當於之前做過一個標記,現在要退回到之前標記的地方 |
| clear() | position = 0;limit = capacity;mark = -1; 有點初始化的味道,但是並不影響底層byte數組的內容 |
| flip() | limit = position;position = 0;mark = -1; 翻轉,也就是讓flip之后的position到limit這塊區域變成之前的0到position這塊,翻轉就是將一個處於存數據狀態的緩沖區變為一個處於准備取數據的狀態 |
| rewind() | 把position設為0,mark設為-1,不改變limit的值 |
| remaining() | return limit - position;返回limit和position之間相對位置差 |
| hasRemaining() | return position < limit返回是否還有未讀內容 |
| compact() | 把從position到limit中的內容移到0到limit-position的區域內,position和limit的取值也分別變成limit-position、capacity。如果先將positon設置到limit,再compact,那么相當於clear() |
| get() | 相對讀,從position位置讀取一個byte,並將position+1,為下次讀寫作准備 |
| get(int index) | 絕對讀,讀取byteBuffer底層的bytes中下標為index的byte,不改變position |
| get(byte[] dst, int offset, int length) | 從position位置開始相對讀,讀length個byte,並寫入dst下標從offset到offset+length的區域 |
| put(byte b) | 相對寫,向position的位置寫入一個byte,並將postion+1,為下次讀寫作准備 |
| put(int index, byte b) | 絕對寫,向byteBuffer底層的bytes中下標為index的位置插入byte b,不改變position |
| put(ByteBuffer src) | 用相對寫,把src中可讀的部分(也就是position到limit)寫入此byteBuffer |
| put(byte[] src, int offset, int length) | 從src數組中的offset到offset+length區域讀取數據並使用相對寫寫入此byteBuffer |
以下為一些測試方法:
public static void main(String args[]){
System.out.println("--------Test reset----------");
buffer.clear();
buffer.position(5);
buffer.mark();
buffer.position(10);
System.out.println("before reset:" + buffer);
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);
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()));
buffer.flip();
System.out.println("after flip:" + buffer);
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()));
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()));
}
