——日拱一卒,不期而至! 你好,我是彤哥,本篇是netty系列的第六篇。 簡介 上一章我們一起學習了Java NIO的核心組件Channel,它可以看作是實體與實體之間的連接,而且需要與Buffer交互,這一章我們就來學習一下Buffer的特性。 概念 Buffer用於與Channel交互時使用,通 ...
——日拱一卒,不期而至!
你好,我是彤哥,本篇是netty系列的第六篇。
簡介
上一章我們一起學習了Java NIO的核心組件Channel,它可以看作是實體與實體之間的連接,而且需要與Buffer交互,這一章我們就來學習一下Buffer的特性。
概念
Buffer用於與Channel交互時使用,通過上一章的學習我們知道,數據從Channel讀取到Buffer,或者從Buffer寫入Channel。
Buffer本質上是一個記憶體塊,可以向裡面寫入數據,或者從裡面讀取數據,在Java中它被包裝成了Buffer對象,並提供了一系列的方法用於操作這個記憶體塊。
屬性
為了更好地理解Buffer的數據結構,我們必須熟悉它的三個常用屬性:
- capacity:容量
- position:當前位置
- limit:限制長度
在讀模式和寫模式下,position和limit的位置有所不同,見下圖:
capacity
Buffer作為一個存儲塊,是有固定大小的,這個固定大小我們稱作“容量”。
當Buffer寫滿之後,需要先清空或者讀取數據,才能繼續寫入新的數據。
position
寫模式下,position從0開始,每寫入一個單位的數據,position前進一位,position最大可到達(capacity-1)的位置。
當Buffer從寫模式切換為讀模式時,position將重置為0。讀取數據時,同樣地,position每讀取一個單位,前進一位,此時,position最大可到達limit的位置(實際最大可讀取的位置是(limit-1))。
limit
寫模式下,limit最大值等於capacity。
讀模式下,limit最大值等於切換為讀模式時position的值,本文來源工從號彤哥讀源碼。
這裡可能有點繞,position類似於數組的下標,是從0開始的,limit表示最大可以讀取或者寫入的長度,capacity表示最大的容量,limit和capacity不是下標,類似於數組的長度,所以跟position比較需要-1。在寫模式下,position指向的是下一個待寫入的位置;在讀模式下,position指向的是下一個待讀取的位置。
類型
Java NIO自帶的Buffer類型有:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
與基本類型一樣,每一種Buffer的基本單位長度不一樣罷了。
其中,MappedByteBuffer是一種特殊的ByteBuffer,它使用記憶體映射的方式載入物理文件,並不會耗費同等大小的物理記憶體,是一種直接操作堆外記憶體的方式,讀寫性能比較高。
基本用法
上面我們學習了Buffer的數據結構以及常用的Buffer類型,它們怎麼使用呢?常見的用法主要有四種:
- 將數據寫入Buffer
- 切換為讀模式flip()
- 從Buffer中讀取數據
- 清空數據並切換為寫模式clear()或者compact()
來個慄子
public class FileChannelTest {
public static void main(String[] args) throws IOException {
// 從文件獲取一個FileChannel
FileChannel fileChannel = new RandomAccessFile("D:\\object.txt", "rw").getChannel();
// 分配一個Byte類型的Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 將FileChannel中的數據讀出到buffer中,-1表示讀取完畢
// buffer預設為寫模式,本文來源工從號彤哥讀源碼
// read()方法是相對channel而言的,相對buffer就是寫
while ((fileChannel.read(buffer)) != -1) {
// buffer切換為讀模式
buffer.flip();
// buffer中是否有未讀數據
while (buffer.hasRemaining()) {
// 讀取數據
System.out.print((char)buffer.get());
}
// 清空buffer,為下一次寫入數據做準備
// clear()會將buffer再次切換為寫模式
buffer.clear();
}
}
}
allocate()
要獲取一個Buffer對象,必須先分配它,每個Buffer類都有一個allocate()方法用於分配Buffer對象。
以下示例分配了一個容量為1024的ByteBuffer對象:
ByteBuffer buffer = ByteBuffer.allocate(1024);
下麵是分配了一個容量為48的CharBuffer的對象:
CharBuffer buf = CharBuffer.allocate(48);
將數據寫入Buffer
將數據寫入Buffer有兩種形式:
- 從Channel讀出數據並寫入Buffer,也叫從Channel讀入Buffer
- 調用Buffer自己的put()方法寫入數據
從Channel讀入Buffer的示例如下:
int bytesRead = inChannel.read(buf); //讀入Buffer
Buffer自己put()寫入數據的示例如下:
buf.put(127);
當然,put()有很多不同的類型,比如在特定位置寫入,寫入不同類型的數據等等,可以在IDEA中按F12查看。
flip()
flip()方法用於將Buffer從寫模式切換為讀模式,position將切換到0位置,且limit將切換到剛纔position的位置。
也就是說,position變成了可讀數據的首位,limit表示可以讀取的最大數據長度。
從Buffer中讀取數據
從Buffer中讀取數據也有兩種形式:
- 從Buffer讀取數據,並寫入Channel,也叫作從Buffer寫入Channel
- 調用Buffer自己的get()方法讀取數據
從Buffer寫入Channel的示例如下:
// 本文來源工從號彤哥讀源碼
int bytesWritten = inChannel.write(buf);
調用Buffer自己的get()方法讀取數據的示例如下:
byte aByte = buf.get();
當然,get()有很多不同的類型,比如從特定的位置讀取,讀取不同類型的數據等等,可以在IDEA中按F12查看。
rewind()
rewind()方法會重置position為0,但limit保持不變,因此可以用來重新讀取數據。通常是在重新讀取數據之前調用。
clear()
clear()方法用於清空整個Buffer,並將Buffer從讀模式切換回寫模式,且position歸位到0位置。
compact()
compact()方法用於清空已讀取的數據,並將未讀取的數據移至Buffer的頭部,position的位置移動到從頭開始計算的未讀取的數據的下一個位置,它也會將Buffer從讀模式切換回寫模式。
mark() 和 reset()
mark()方法用於標記給定位置,然後可以在之後通過reset()方法重新回到mark的位置,示例如下:
buffer.mark();
//多次調用buffer.get(),例如在解析過程中。
buffer.reset(); //將位置重新設置為標記。
總結
今天我們學習了Java NIO核心組件Buffer,它經常跟Channel聯合起來使用。講到這裡我們一直在使用FileChannel在舉例,那麼它們到底跟網路編程有什麼關係呢?請聽下回分解。
參考
http://tutorials.jenkov.com/java-nio/channels.html
最後,也歡迎來我的工從號彤哥讀源碼系統地學習源碼&架構的知識。