1. 程式人生 > >28 Java學習之NIO Buffer(二)(待補充)

28 Java學習之NIO Buffer(二)(待補充)

客戶 oca opened output write 系統方面 eba 了解 取出

一. 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 緩沖區讀模式下的限制:13
View 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

28 Java學習之NIO Buffer(二)(待補充)