【Java】Java NIO 之 Buffer 緩衝區(二)
一、Buffer(緩衝區)介紹
1.1、Buffer(緩衝區)介紹
Java NIO Buffers用於和NIO Channel互動。 我們從Channel中讀取資料到buffers裡,從Buffer把資料寫入到Channels.
Buffer本質上就是一塊記憶體區,可以用來寫入資料,並在稍後讀取出來。這塊記憶體被NIO Buffer包裹起來,對外提供一系列的讀寫方便開發的介面。
在Java NIO中使用的核心緩衝區如下(覆蓋了通過I/O傳送的基本資料型別:byte, char、short, int, long, float, double ,long)
-
ByteBuffer
-
CharBuffer
-
ShortBuffer
-
IntBuffer
-
FloatBuffer
-
DoubleBuffer
-
LongBuffer
1.2、Buffer的基本用法
使用Buffer讀寫資料一般遵循以下四個步驟:
- 寫入資料到Buffer
- 呼叫
flip()
方法 - 從Buffer中讀取資料
- 呼叫
clear()
方法或者compact()
方法
當向buffer寫入資料時,buffer會記錄下寫了多少資料。一旦要讀取資料,需要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到buffer的所有資料。
一旦讀完了所有的資料,就需要清空緩衝區,讓它可以再次被寫入。有兩種方式能清空緩衝區:呼叫clear()或compact()方法。clear()方法會清空整個緩衝區。compact()方法只會清除已經讀過的資料。任何未讀的資料都被移到緩衝區的起始處,新寫入的資料將放到緩衝區未讀資料的後面。
1.3、Buffer的容量,位置,上限(Buffer Capacity, Position and Limit)
Buffer緩衝區實質上就是一塊記憶體,用於寫入資料,也供後續再次讀取資料。這塊記憶體被NIO Buffer管理,並提供一系列的方法用於更簡單的操作這塊記憶體。
一個Buffer有三個屬性是必須掌握的,分別是:
-
capacity(容量)
-
position(位置)
-
limit(限制)
position和limit的具體含義取決於當前buffer的模式。capacity在兩種模式下都表示容量。
這裡有一個關於capacity,position和limit在讀寫模式中的說明,詳細的解釋在插圖後面。
capacity(容量)
作為一個記憶體塊,Buffer有一個固定的大小值,也叫“capacity”.你只能往裡寫capacity個byte、long,char等型別。一旦Buffer滿了,需要將其清空(通過讀資料或者清除資料)才能繼續寫資料往裡寫資料。
position(位置)
當你寫資料到Buffer中時,position表示當前的位置。初始的position值為0.當一個byte、long等資料寫到Buffer後, position會向前移動到下一個可插入資料的Buffer單元。
position最大可為capacity-1.
當讀取資料時,也是從某個特定位置讀。當將Buffer從寫模式切換到讀模式,position會被重置為0. 當從Buffer的position處讀取資料時,position向前移動到下一個可讀的位置。
limit(限制)
在寫模式下,Buffer的limit表示你最多能往Buffer裡寫多少資料。 寫模式下,limit等於Buffer的capacity。
當切換Buffer到讀模式時, limit表示你最多能讀到多少資料。因此,當切換Buffer到讀模式時,limit會被設定成寫模式下的position值。換句話說,你能讀到之前寫入的所有資料(limit被設定成已寫資料的數量,這個值在寫模式下就是position)
二、Buffer的常見方法
方法 | 介紹 |
---|---|
abstract Object array() | 返回支援此緩衝區的陣列 (可選操作) |
abstract int arrayOffset() | 返回該緩衝區的緩衝區的第一個元素的在陣列中的偏移量 (可選操作) |
int capacity() | 返回此緩衝區的容量 |
Buffer clear() | 清除此快取區。將position = 0;limit = capacity;mark = -1; |
Buffer flip() | flip()方法可以吧Buffer從寫模式切換到讀模式。呼叫flip方法會把position歸零,並設定limit為之前的position的值。 也就是說,現在position代表的是讀取位置,limit標示的是已寫入的資料位置。 |
abstract boolean hasArray() | 告訴這個緩衝區是否由可訪問的陣列支援 |
boolean hasRemaining() | return position < limit,返回是否還有未讀內容 |
abstract boolean isDirect() | 判斷個緩衝區是否為 direct |
abstract boolean isReadOnly() | 判斷告知這個緩衝區是否是隻讀的 |
int limit() | 返回此緩衝區的限制 |
Buffer position(int newPosition) | 設定這個緩衝區的位置 |
int remaining() | return limit - position; 返回limit和position之間相對位置差 |
Buffer rewind() | 把position設為0,mark設為-1,不改變limit的值 |
Buffer mark() | 將此緩衝區的標記設定在其位置 |
三、Buffer的使用方式/方法介紹
3.1、分配緩衝區(Allocating a Buffer)
為了獲得緩衝區物件,我們必須首先分配一個緩衝區。在每個Buffer類中,allocate()方法用於分配緩衝區。
下面是一個分配48位元組capacity的ByteBuffer的例子:
1 ByteBuffer buf = ByteBuffer.allocate(48);
這是分配一個可儲存1024個字元的CharBuffer:
1 CharBuffer buf = CharBuffer.allocate(1024);
3.2、寫入資料到緩衝區(Writing Data to a Buffer)
寫資料到Buffer有兩種方法:
-
從Channel中寫資料到Buffer
-
手動寫資料到Buffer,呼叫put方法
從Channel寫到Buffer的例子
1 int bytesRead = inChannel.read(buf); //read into buffer.
通過put方法寫Buffer的例子:
1 buf.put(127);
put方法有很多版本,允許你以不同的方式把資料寫入到Buffer中。例如, 寫到一個指定的位置,或者把一個位元組陣列寫入到Buffer。 更多Buffer實現的細節參考JavaDoc。
3.3、翻轉(flip())
flip方法將Buffer從寫模式切換到讀模式。呼叫flip()方法會將position設回0,並將limit設定成之前position的值。
換句話說,position現在用於標記讀的位置,limit表示之前寫進了多少個byte、char等 —— 現在能讀取多少個byte、char等。
3.4、從Buffer讀取資料(Reading Data from a Buffer)
從Buffer讀資料也有兩種方式。
-
從buffer讀資料到channel
-
從buffer直接讀取資料,呼叫get方法
從Buffer讀取資料到Channel的例子:
1 //read from buffer into channel. 2 int bytesWritten = inChannel.write(buf);
使用get()方法從Buffer中讀取資料的例子
1 byte aByte = buf.get();
get方法有很多版本,允許你以不同的方式從Buffer中讀取資料。例如,從指定position讀取,或者從Buffer中讀取資料到位元組陣列。更多Buffer實現的細節參考JavaDoc。
3.5、rewind()方法
Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有資料。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
3.6、clear()與compact()方法
一旦讀完Buffer中的資料,需要讓Buffer準備好再次被寫入。可以通過clear()或compact()方法來完成。
如果呼叫的是clear()方法,position將被設回0,limit被設定成 capacity的值。換句話說,Buffer 被清空了。Buffer中的資料並未清除,只是這些標記告訴我們可以從哪裡開始往Buffer裡寫資料。
如果Buffer中有一些未讀的資料,呼叫clear()方法,資料將“被遺忘”,意味著不再有任何標記會告訴你哪些資料被讀過,哪些還沒有。
如果Buffer中仍有未讀的資料,且後續還需要這些資料,但是此時想要先先寫些資料,那麼使用compact()方法。
compact()方法將所有未讀的資料拷貝到Buffer起始處。然後將position設到最後一個未讀元素正後面。limit屬性依然像clear()方法一樣,設定成capacity。現在Buffer準備好寫資料了,但是不會覆蓋未讀的資料。
3.7、mark()與reset()方法
通過呼叫Buffer.mark()方法,可以標記Buffer中的一個特定position。之後可以通過呼叫Buffer.reset()方法恢復到這個position。例如:
1 buffer.mark(); 2 3 //call buffer.get() a couple of times, e.g. during parsing. 4 5 buffer.reset(); //set position back to mark.
3.8、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常用方法測試
這裡以ByteBuffer為例子說明抽象類Buffer的實現類的一些常見方法的使用:
1 /** 2 * 1、緩衝區(Buffer): 3 * 在Java NIO 中負責資料的存取,緩衝區九是陣列,用於儲存不同資料型別的資料 4 * 根據資料型別不同(boolean除外),提供了相應型別的緩衝區 5 * ByteBuffer 6 * CharBuffer 7 * ShortBuffer 8 * IntBuffer 9 * LongBuffer 10 * FloatBuffer 11 * DoubleBuffer 12 * 13 * 2、緩衝區存取資料的兩個核心方法: 14 * put() :存入資料到緩衝區中 15 * get():獲取緩衝區中的資料 16 * 17 * 3、緩衝區中的四個核心屬性: 18 * capacity:容量,表示緩衝區中最大儲存資料的容量。一旦宣告不能改變 19 * limit:界限,表示緩衝區中可以操作資料的大小,代表了當前緩衝區中一共有多少資料。。(limit 後資料不能進行讀寫) 20 * position:位置,表示緩衝區中正在操作資料的位置,Position會自動由相應的 get( )和 put( )函式更新 21 * mark:標記,一個備忘位置。用於記錄當前position位置,可以通過過reset() 恢復到 mark 的位置。 22 * 23 * 規律 24 * mark <= position <= limit <= capacity 25 * 26 * 4、直接緩衝區與非直接緩衝區 27 * 非直接緩衝區:通過allocate() 方法分配,將緩衝區建立在 JVM 的記憶體中 28 * 直接緩衝區:通過allocateDirect() 方法分配,將緩衝區建立在實體記憶體中,可以提高效率 29 * 30 * 31 */ 32 public class BufferTest { 33 34 /** 35 * 測試常用方法 36 */ 37 @Test 38 public void test1(){ 39 // 1、分配一個指定大小的快取區 40 ByteBuffer buf = ByteBuffer.allocate(5); 41 42 System.out.println("----------allocate()---------"); 43 System.out.println("position == " + buf.position()); 44 System.out.println("limit == " + buf.limit()); 45 System.out.println("capacity == " + buf.capacity()); 46 47 48 // 2、利用put() 存入資料到緩衝區區 49 buf.put("abcde".getBytes()); 50 51 System.out.println("----------put()---------"); 52 System.out.println("position == " + buf.position()); 53 System.out.println("limit == " + buf.limit()); 54 System.out.println("capacity == " + buf.capacity()); 55 56 // 3、利用flip() 切換讀取資料模式 57 buf.flip(); 58 59 System.out.println("----------flip()---------"); 60 System.out.println("position == " + buf.position()); 61 System.out.println("limit == " + buf.limit()); 62 System.out.println("capacity == " + buf.capacity()); 63 64 // 4、利用get() 讀取緩衝區中的資料 65 byte[] dst = new byte[buf.limit()]; 66 buf.get(dst); 67 68 System.out.println("----------get()---------"); 69 System.out.println("position == " + buf.position()); 70 System.out.println("limit == " + buf.limit()); 71 System.out.println("capacity == " + buf.capacity()); 72 73 System.out.println(new String(dst, 0, dst.length)); 74 75 // 5、利用rewind() 回到讀模式(可重複讀資料) 76 buf.rewind(); 77 78 System.out.println("----------rewind()---------"); 79 System.out.println("position == " + buf.position()); 80 System.out.println("limit == " + buf.limit()); 81 System.out.println("capacity == " + buf.capacity()); 82 83 // 6、利用clear() 清空緩衝區,當是緩衝區的資料還在,可看原始碼 84 buf.clear(); 85 86 System.out.println("----------clear()---------"); 87 System.out.println("position == " + buf.position()); 88 System.out.println("limit == " + buf.limit()); 89 System.out.println("capacity == " + buf.capacity()); 90 91 // 測試資料並未被清空 92 System.out.println((char)buf.get()); 93 94 } 95 96 /** 97 * 測試mark 方法 98 */ 99 @Test 100 public void test2(){ 101 ByteBuffer buf = ByteBuffer.allocate(1024); 102 buf.put("abcdefg".getBytes()); 103 buf.flip(); 104 105 byte[] dst = new byte[buf.limit()]; 106 buf.get(dst, 0, 2); 107 System.out.println(new String(dst, 0, 2)); 108 System.out.println("position === " + buf.position()); 109 110 buf.mark(); 111 112 buf.get(dst, 2, 2); 113 System.out.println(new String(dst, 2, 2)); 114 System.out.println("position === " + buf.position()); 115 116 // 重置到mark的位置 117 buf.reset(); 118 System.out.println("position === " + buf.position()); 119 120 121 // 判斷緩衝區中是否還有剩餘資料 122 if(buf.hasRemaining()) { 123 // 獲取緩衝區中,可以操作的資料數量 124 System.out.println("remaining === " + buf.remaining()); 125 } 126 } 127 128 /** 129 * 直接緩衝區與非直接緩衝區 130 */ 131 @Test 132 public void test3(){ 133 ByteBuffer buf = ByteBuffer.allocateDirect(1024); 134 135 System.out.println(buf.isDirect()); 136 } 137 }