1. 程式人生 > 程式設計 >6. 彤哥說netty系列之Java NIO核心元件之Buffer

6. 彤哥說netty系列之Java NIO核心元件之Buffer

——日拱一卒,不期而至!

nio

你好,我是彤哥,本篇是netty系列的第六篇。

簡介

上一章我們一起學習了Java NIO的核心元件Channel,它可以看作是實體與實體之間的連線,而且需要與Buffer互動,這一章我們就來學習一下Buffer的特性。

概念

Buffer用於與Channel互動時使用,通過上一章的學習我們知道,資料從Channel讀取到Buffer,或者從Buffer寫入Channel。

nio

Buffer本質上是一個記憶體塊,可以向裡面寫入資料,或者從裡面讀取資料,在Java中它被包裝成了Buffer物件,並提供了一系列的方法用於操作這個記憶體塊。

屬性

為了更好地理解Buffer的資料結構,我們必須熟悉它的三個常用屬性:

  • capacity:容量
  • position:當前位置
  • limit:限制長度

在讀模式和寫模式下,position和limit的位置有所不同,見下圖:

nio

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()

來個栗子

nio

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

最後,也歡迎來我的工從號彤哥讀原始碼系統地學習原始碼&架構的知識。

nio