1. 程式人生 > 實用技巧 >【Java】Java NIO 之 Buffer 緩衝區(二)

【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讀寫資料一般遵循以下四個步驟:

  1. 寫入資料到Buffer
  2. 呼叫flip()方法
  3. 從Buffer中讀取資料
  4. 呼叫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相等:

  1. 有相同的型別(byte、char、int等)。
  2. Buffer中剩餘的byte、char等的個數相等。
  3. Buffer中所有剩餘的byte、char等都相同。

  如你所見,equals只是比較Buffer的一部分,不是每一個在它裡面的元素都比較。實際上,它只比較Buffer中的剩餘元素。

  compareTo()方法

  compareTo()方法比較兩個Buffer的剩餘元素(byte、char等), 如果滿足下列條件,則認為一個Buffer“小於”另一個Buffer:

  1. 第一個不相等的元素小於另一個Buffer中對應的元素 。
  2. 所有元素都相等,但第一個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 }