28 Java學習之NIO Buffer(二)(待補充)
一. Buffer介紹
Buffer,故名思意,緩衝區,實際上是一個容器,是一個連續陣列。Channel提供從檔案、網路讀取資料的渠道,但是讀取或寫入的資料都必須經由Buffer。具體看下面這張圖就理解了:
上面的圖描述了從一個客戶端向服務端傳送資料,然後服務端接收資料的過程。客戶端傳送資料時,必須先將資料存入Buffer中,然後將Buffer中的內容寫入通道。服務端這邊接收資料必須通過Channel將資料讀入到Buffer中,然後再從Buffer中取出資料來處理。
Buffer本質上就是一塊記憶體區,可以用來寫入資料,並在稍後讀取出來。這塊記憶體被NIO Buffer包裹起來,對外提供一系列的讀寫方便開發的介面。
在NIO中,Buffer是一個頂層父類,它是一個抽象類,常用的Buffer的子類有:
- ByteBuffer
- IntBuffer
- CharBuffer
- LongBuffer
- DoubleBuffer
- FloatBuffer
- ShortBuffer
如果是對於檔案讀寫,上面幾種Buffer都可能會用到。但是對於網路讀寫來說,用的最多的是ByteBuffer。
位元組緩衝區和其他緩衝區型別最明顯的不同在於,它們可能成為通道所執行I/O的源頭或目標,如果對NIO有了解的朋友們一定知道,通道只接收ByteBuffer作為引數。如我們所知道的,作業系統在記憶體區域進行I/O操作,這些記憶體區域,就作業系統方面而言,是相連的位元組序列。於是,毫無疑問,只有位元組緩衝區有資格參與I/O操作
二. Buffer的屬性
- capacity(容量):buffer本質是一個數組,在初始化時有固定的大小,這個值就是容量。容量不可改變,一旦緩衝區滿了,需要將其清空才能將繼續進行讀寫操作。
ByteBuffer bf = ByteBuffer.allocate(10); //建立了一個最大容量為10的位元組緩衝區
- position(位置):表示當前的位置,初始化時為0,當一個基本資料型別的資料寫入buffer時,position會向前移動到下一個可插入資料的Buffer單元。position最大值可以是 capacity-1。
- limit(限制):在緩衝區寫模式下,limit表示你最多能往Buffer裡寫多少資料,大小等於capacity;在緩衝區讀模式下,limit表示能從緩衝區內讀取到多少資料,因此,當切換Buffer到讀模式時,limit會被設定成寫模式下的position值。可以認為是緩衝區中實際元素的數量
- mark(標記):一個備忘位置,呼叫mark()方法的話,mark值將儲存當前position的值,等下次呼叫reset()方法時,會設定position的值為之前的標記值。
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.nio.ByteBuffer; 8 import java.nio.channels.FileChannel; 9 10 public class Test { 11 public static void main(String[] args) throws IOException{ 12 FileInputStream fileInputStream=new FileInputStream("C:\\Users\\hermioner\\Desktop\\in.txt"); 13 FileChannel fileChannel=fileInputStream.getChannel(); 14 15 FileOutputStream fileOutputStream=new FileOutputStream("C:\\Users\\hermioner\\Desktop\\out.txt"); 16 FileChannel fileChannel2=fileOutputStream.getChannel(); 17 18 //初始化緩衝區 19 ByteBuffer buffer=ByteBuffer.allocate(20); 20 System.out.println("通道檔案的大小:"+fileChannel.size()); 21 System.out.println("緩衝區初始化時當前位置:"+buffer.position()); 22 System.out.println("緩衝區初始化時可寫的限制:"+buffer.limit()); 23 System.out.println("------------迴圈開始---------"); 24 25 //判斷通道內資料是否讀取完成 26 while(-1 != fileChannel.read(buffer)) 27 { 28 System.out.println("緩衝區寫模式下當前位置:"+buffer.position()); 29 System.out.println("緩衝區寫模式下的限制:"+buffer.limit()); 30 31 //將緩衝區從寫模式切換到讀模式 32 buffer.flip(); 33 System.out.println("緩衝區讀模式下當前位置:"+buffer.position()); 34 System.out.println("緩衝區讀模式下的限制:"+buffer.limit()); 35 36 //判斷緩衝區內是否還有資料可讀取 37 while(buffer.hasRemaining()) { 38 fileChannel2.write(buffer); 39 } 40 buffer.clear(); 41 } 42 fileChannel.close(); 43 fileChannel2.close(); 44 fileOutputStream.close(); 45 fileInputStream.close(); 46 47 } 48 }View Code
1 通道檔案的大小:13 2 緩衝區初始化時當前位置:0 3 緩衝區初始化時可寫的限制:20 4 ------------迴圈開始--------- 5 緩衝區寫模式下當前位置:13 6 緩衝區寫模式下的限制:20 7 緩衝區讀模式下當前位置:0 8 緩衝區讀模式下的限制:13View Code
執行結果,out.txt檔案中的內容和in.txt檔案中的內容一樣樣,且為:
hello,
world
說明:可以看到,在緩衝區寫模式下,limit的大小始終等於capacity;而在讀模式下,limit等於模式切換前position的大小
position <= limit <= capacity
再舉個例子,觀察四個值得變化:
(1)建立一個容量大小為10的位元組緩衝區
ByteBuffer bf = ByteBuffer.allocate(10);
此時:mark = -1; position = 0; limit =10 ; capacity = 10;
(2)往緩衝區中put五個位元組
bf.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
注意這裡一個字元是佔用兩個位元組的,但是英文字元只佔用一個位元組,所以這樣是可以實現儲存效果的。
此時:此時:mark = -1; position = 5; limit = 10; capacity = 10;
(3)呼叫flip方法,切換為讀就緒狀態
bf.flip();
此時:mark = -1; position = 0; limit = 5; capacity = 10;
(4)讀取兩個元素
System.out.println("" + (char) bf.get() + (char) bf.get());
此時:mark = -1; position = 2; limit = 5; capacity = 10;
(5)標記此時的position位置
bf.mark();
此時:mark = 2; position = 2; limit = 5; capacity = 10;
(6)讀取兩個元素後,恢復到之前mark的位置處
System.out.println("" + (char) bf.get() + (char) bf.get()); bf.reset();
執行完第二行程式碼:mark = 2; position = 2; limit = 5; capacity = 10;
(7)呼叫compact方法,釋放已讀取資料的空間,準備重新填充快取區
bf.compact();
此時:mark = 2; position = 3; limit = 10; capacity = 10;
注意觀察陣列中元素的變化,實際上進行了陣列拷貝,拋棄了已讀位元組元素,保留了未讀位元組元素;
三. Buffer中的重要方法
3.1 flip
buffer.flip()該方法是用於將緩衝區從寫模式切換到讀模式,這是一種固定寫法
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
3.2 remaind方法
public final Buffer rewind() { position = 0; mark = -1; return this; }
將position的位置設定為0,表示可以重新讀取Buffer中的所有資料,limit保持不變。
3.3 clear方法
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
- 一旦完成對buffer中資料的讀取,需要讓buffer做好再次被寫入的準備,這時候可以呼叫clear方法來完成。
- clear方法將position設定為0,limit設定為容量的值,也就意味著buffer被清空了,但是這個清空的概念是寫入資料可以從緩衝區的指定位置開始,但buffer裡面的資料並沒有 刪除。
- 如果buffer裡面還有資料沒有被讀取,這個時候呼叫clear方法會導致那些資料被“遺忘”,因為沒有標記告訴你哪些是讀取過哪些沒有被讀取。
3.4 向buffer中寫入資料
- 通過channel寫入;
- 通過buffer的put方法寫入:
buffer.put("channel".getBytes());
3.5 讀取buffer中的資料
- 通過channel讀取;
- 通過buffer的get方法讀取:
byte b = buffer.get();
四. 利用Buffer讀寫資料,通常遵循四個步驟
-
把資料寫入buffer;
-
呼叫flip;
-
從Buffer中讀取資料;
-
呼叫buffer.clear()或者buffer.compact()。
當寫入資料到buffer中時,buffer會記錄已經寫入的資料大小。當需要讀資料時,通過 flip() 方法把buffer從寫模式調整為讀模式;在讀模式下,可以讀取所有已經寫入的資料。
當讀取完資料後,需要清空buffer,以滿足後續寫入操作。清空buffer有兩種方式:呼叫 clear() 或 compact() 方法。clear會清空整個buffer,compact則只清空已讀取的資料,未被讀取的資料會被移動到buffer的開始位置,寫入位置則近跟著未讀資料之後。
參考文獻:
https://www.cnblogs.com/dolphin0520/p/3919162.html
https://www.cnblogs.com/dongguacai/p/5812831.html
https://www.cnblogs.com/chenpi/p/6475510.html