1. 程式人生 > >Java NIO系列(二) - Buffer

Java NIO系列(二) - Buffer

ava adf get() con eap 本質 close 方法 rtb

前言

Java NIO中,緩沖區用來臨時存儲數據,可以理解為是I/O操作中數據暫存的中轉站。緩沖區直接為通道(Channel)服務,數據是從通道讀入緩沖區,從緩沖區寫入到通道中的。

緩沖區本質上是一塊可以寫入數據,然後可以從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,並提供了一組方法,用來方便的訪問這塊內存

正文

Buffer的類型

Java NIO提供以下幾種Buffer類型:

技術分享圖片

  • ByteBuffer
  • MappedByteBuffer
  • ShortBuffer
  • LongBuffer
  • FloatBuffer
  • CharBuffer
  • IntBuffer
  • DoubleBuffer

這些Buffer類型代表了Java中7種基本數據類型。換句話說,就是可以通過bytecharshortintlongfloatdouble類型來操作緩沖區中的數據。

Buffer的基本用法

使用Buffer讀寫數據一般遵循以下四個步驟:

  1. 寫入數據到Buffer中;
  2. 調用Bufferflip()方法;
  3. Buffer中讀取數據;
  4. 調用clear()方法或者compact()方法。

當向Buffer寫入數據時,Buffer會記錄下寫了多少數據。一旦要讀取數據,需要通過flip()方法將Buffer寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到Buffer的所有數據。

一旦讀完了所有的數據,就需要清空緩沖區

,讓它可以再次被寫入。兩種方式能清空緩沖區:調用clear()compact()方法。

  • clear()方法:清空整個緩沖區,包括已讀未讀的數據。

  • compact()方法:只會清空已讀的數據,未讀的數據都被移到緩沖區起始處,新寫入的數據將放到緩沖區未讀數據後面

下面給出一個ByteBuffer的簡單使用示例,其他緩沖區API的使用類似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void testReadFromBuffer() {
try {
RandomAccessFile file = new RandomAccessFile("D://test.txt", "rw");
FileChannel fileChannel = file.getChannel();
//創建容量為10byte的buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 不斷地寫入緩沖區,寫一次讀一次
while (fileChannel.read(byteBuffer) != -1) {
// 設置buffer切換模式為讀模式
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
// 每次讀取1byte,也就是一個字節
System.out.print((char) byteBuffer.get());
}
// 清空整個緩存區,準備下次寫入
byteBuffer.clear();
}
fileChannel.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

Buffer的重要屬性

為了理解Buffer的工作原理,需要熟悉它的4個核心屬性:

屬性含義具體描述
capacity 容量 緩沖區可以容納的最大數據量,在緩沖區創建時被設定並且不能改變
limit 上界 緩沖區中當前已使用的數據量
position 位置 緩沖區下一個要被讀或寫的元素的索引
mark 標記 調用mark()來設置mark=position,再調用reset()可以讓position恢復到標記的位置即position=mark

其中,positionlimit的含義取決於Buffer處在讀模式還是寫模式。不管Buffer處在什麽模式,capacity的含義總是一樣的。

技術分享圖片

capacity

作為一個內存塊Buffer有一個固定的大小值,也叫capacity。你最多只能寫入capacity個的bytecharintlong等類型數據。一旦Buffer滿了,需要將其清空(通過讀數據或者清除數據)才能繼續往裏寫數據。

position

  • 寫入數據時

當你寫數據到Buffer中時,position表示下一個可寫入的數據的位置。position初始位置0,當一個bytecharintlong等數據寫到Buffer後,position會向前移動到下一個可插入數據的Buffer單元。position最大可為capacity – 1

  • 讀取數據時

當從Buffer讀取數據時,position表示下一個可讀取的數據的位置。當將Buffer寫模式切換到讀模式position會被重置為0。當從Bufferposition讀取數據時,position向前移動到下一個可讀的位置。

limit

  • 寫入數據時

寫模式下,Bufferlimit表示你最多能往Buffer裏寫多少數據。寫模式下,limit等於Buffercapacity,也就是內存塊的最大容量。

  • 寫入數據時

當切換Buffer到讀模式時,limit會被設置成寫模式下的position值,limit表示你最多能讀到多少數據。limit被設置成已寫數據的數量,這個值在寫模式下就是position

Buffer的方法

Buffer的分配

要想獲得一個Buffer對象首先要進行分配,每一個Buffer類都有一個allocate()方法。下面是一個分配10字節capacityByteBuffer的例子:

1
ByteBuffer buf = ByteBuffer.allocate(10);

這是分配一個可存儲1024個字符的CharBuffer

1
CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中寫入數據

寫數據到Buffer有兩種方式:

  • Channel將數據寫入Buffer
1
int bytesRead = channel.read(buffer);
  • 通過Bufferput()方法寫到Buffer中。
1
buffer.put(1);

put()ByteBuffer中為抽象方法,在ByteBuffer有很多的重載,由其子類HeapByteBufferDirectByteBuffer實現。

寫模式切換為讀模式

flip()方法將Buffer寫模式切換到讀模式。調用flip()方法會將position設回0,並將limit設置成之前position的值。

1
buffer.flip();

查看flip()方法的源碼確認:

1
2
3
4
5
6
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}

從Buffer從讀取數據

Buffer中讀取數據也有兩種方式:

  • Buffer讀取數據到Channel中。
1
int bytesWritten = channel.write(buf);
  • 通過Bufferget()方法從Buffer中讀取數據。
1
byte b = buffer.get();

get()方法和put()一樣有很多的重載,允許以不同的方式從Buffer中讀取數據。例如:從指定position讀取,或者從Buffer中讀取數據到字節數組。

clear()和compact()方法

一旦讀完Buffer中的數據,需要讓Buffer準備好再次被寫入。前面也說了,可以通過clear()compact()方法來完成。

clear()方法

如果調用的是clear()方法,position將被設回0limit被設置成capacity的值。

1
buffer.clear();

查看clear()方法的源碼確認:

1
2
3
4
5
6
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}

換句話說,Buffer被清空了。Buffer中的數據並未清除,只是這些標記告訴我們可以從哪裏開始往Buffer裏寫數據。

compact()方法

如果調用的是compact()方法,所有的未讀數據都將被拷貝到Buffer的起始位置,position會設置為最後一個未讀元素的後面。limit()方法和clear()方法一樣,會被設置為capacity的大小。

查看compact()方法的實現,此方法在ByteBuffer中為抽象方法,查看其子類HeapByteBuffer的實現:

1
2
3
4
5
6
7
8
9
10
11
public ByteBuffer compact() {
// 將未讀的數據往前移動
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
// 設置postion為最後一個未讀數據後面的位置
position(remaining());
// 設置limit為最大的容量
limit(capacity());
// 清除標記位
discardMark();
return this;
}

現在Buffer準備好寫數據了,但是不會覆蓋未讀的數據。

mark()與reset()方法

通過調用mark()方法,可以標記Buffer中的一個特定position。之後可以通過調用reset()方法恢復到這個position。例如:

mark()方法

1
buffer.mark();

查看mark()方法的源碼,mark變量被設置為position的值:

1
2
3
4
public final Buffer mark() {
mark = position;
return this;
}

reset()方法

1
buffer.reset();

查看mark()方法的源碼,position變量被設置為之前的mark的值:

1
2
3
4
5
6
7
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}

equals()與compareTo()方法

可以使用equals()compareTo()方法比較兩個Buffer

equals()方法

當同時滿足下列條件時,表示兩個Buffer相等:

  1. 有相同的類型(bytecharintlong類型等)。
  2. Buffer中剩余的bytechar等元素的個數相等。
  3. Buffer中所有剩余的bytechar等都相同。

equals()方法比較的實際是Buffer中的剩余元素是否相等。它只是比較Buffer的一部分,不是每一個在它裏面的元素都比較。

compareTo()方法

compareTo()方法比較兩個Buffer的剩余元素(bytechar等)。
當滿足下列條件時,則認為一個Buffer小於另一個Buffer

  1. 第一個不相等的元素小於另一個Buffer中對應的元素。
  2. 所有元素都相等,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數比另一個少)。

總結

這裏只是對Buffer進行了入門的介紹,具體深入學習還需要查看各種緩沖區以及相關的具體實現。


歡迎關註技術公眾號: 零壹技術棧

技術分享圖片零壹技術棧

本帳號將持續分享後端技術幹貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分布式和微服務,架構學習和進階等學習資料和文章。

Java NIO系列(二) - Buffer