Java ByteBuffer 分析
1. 前言
基于 java.net.Socket 進(jìn)行 TCP Socket 編程,我們是通過 java.net.SocketInputStream 和 java.net.SocketOutputStream 實現(xiàn)數(shù)據(jù)讀寫,這是基于字節(jié)流的數(shù)據(jù)讀寫,每次讀寫固定的幾個字節(jié),沒有緩沖能力。為了提高 I/O 讀寫性能,我們引入了 BufferedInputStream & BufferedOutputStream。緩沖流本質(zhì)是提供了一個較大塊內(nèi)存,實現(xiàn)大塊數(shù)據(jù)讀寫的能力。Java NIO 提供的 SocketChannel 是基于 ByteBuffer 實現(xiàn)數(shù)據(jù)讀寫的,天生具有緩沖能力,畢竟 Java NIO 就是為了解決性能問題的嘛。如果從 SocketChannel 讀取數(shù)據(jù),必須預(yù)先分配好 ByteBuffer;如果要想將數(shù)據(jù)寫入 SocketChannel,必須預(yù)先將數(shù)據(jù)寫入 ByteBuffer。
Java NIO 定義了一個關(guān)于緩沖的抽象類是 java.nio.Buffer,Buffer 有很多實現(xiàn)子類,ByteBuffer 僅僅是其中一個子類。下來我們就對 java.nio.ByteBuffer 的功能做一個介紹。
2. java.nio.Buffer 基本結(jié)構(gòu)
java.nio.Buffer 是一個抽象類,定義了 Buffer 的基本結(jié)構(gòu)。Buffer 存放的內(nèi)容是 Java 的基本類型,針對每一個基本類型,都有一個實現(xiàn)類。比如,LongBuffer,IntBuffer,ByteBuffer 等。Buffer 是一個線性結(jié)構(gòu),內(nèi)部實現(xiàn)是一個數(shù)組,是有大小限制的。java.nio.Buffer 中定義了幾個非常重要的屬性,聲明如下:
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
- capacity,表示 ByteBuffer 的容量,即 Buffer 總大小。
- position,表示 Buffer 當(dāng)前數(shù)據(jù)讀寫的位置。position 的取值不會是負(fù)數(shù),也不會超過 limit 的取值。
- limit,表示 Buffer 讀寫操作的結(jié)束位置。limit 的取值不會是負(fù)數(shù),也不會超過 capacity 的取值。
- mark,用于用戶自定義的標(biāo)記位置。
3. java.nio.ByteBuffer 介紹
java.nio.ByteBuffer 中存儲的內(nèi)容是 java 的基本類型 byte。一個非空的 ByteBuffer 的結(jié)構(gòu)如下:
ByteBuffer 內(nèi)部維護(hù)了一個 byte 數(shù)組,position、limit、capacity 都是數(shù)組的下標(biāo)。
3.1 ByteBuffer 創(chuàng)建
ByteBuffer 本身也是一個抽象類,沒有提供 public 構(gòu)造方法,創(chuàng)建 ByteBuffer 必須通過它的工廠方法完成。如果是要創(chuàng)建一個新的、空的 ByteBuffer,可以調(diào)用它的 allocate 方法。如果是想把一個已知的 byte 數(shù)組包裝到 ByteBuffer 中,而不是重新分配內(nèi)存空間,可以調(diào)用它的 wrap 方法。聲明如下:
public static ByteBuffer allocate(int capacity)
public static ByteBuffer wrap(byte[] array, int offset, int length)
public static ByteBuffer wrap(byte[] array)
allocate 方法包含一個 capacity 參數(shù),需要用戶指定 ByteBuffer 的大小。 wrap 方法有兩個重載實現(xiàn),都需要傳入數(shù)組的引用,另外一個還可以指定一個 offset 和 length。示例代碼如下:
ByteBuffer newBuffer = ByteBuffer.allocate(1024);
byte[] tmpByteArray = new byte[512];
ByteBuffer wrapBuffer = ByteBuffer.wrap(tmpByteArray);
新創(chuàng)建的 ByteBuffer,capacity、position、limit 的取值如下圖所示:
position 指向數(shù)組開頭,capacity 和 limit 都指向數(shù)組結(jié)尾。
3.2 向 ByteBuffer 寫入數(shù)據(jù)
ByteBuffer 提供了一組重載的、寫入數(shù)據(jù)的方法,你可以寫入單個 byte,也可以寫入一個 byte 數(shù)組。聲明如下:
public abstract ByteBuffer put(byte b);
public final ByteBuffer put(byte[] src)
示例代碼如下:
tmpByteArray[0] = (byte)0x11;
tmpByteArray[1] = (byte)0x22;
newBuffer.put((byte)0xAA);
newBuffer.put((byte)0xBB);
newBuffer.put(tmpByteArray, 0, 2);
經(jīng)過 put 操作后, newBuffer 的 capacity、position、limit 的值如下圖所示:
當(dāng)我們向 newBuffer 中 put 4 個 byte 類型的數(shù)據(jù)后,position 指向 4,capacity 和 limit 沒有變化。
3.3 ByteBuffer 的 flip 方法
如果要從一個寫入數(shù)據(jù)的 ByteBuffer 讀取數(shù)據(jù),需要調(diào)用 ByteBuffer 的 flip 方法。flip 方法會改變 capacity、position、limit 的值。示例代碼:
newBuffer.flip();
調(diào)用完 flip 方法后,newBuffer 的 capacity、position、limit 的值如下圖所示:
3.4 從 ByteBuffer 讀取數(shù)據(jù)
ByteBuffer 提供了一組重載的、讀取數(shù)據(jù)的方法,你可以讀取單個 byte,也可以讀取一個 byte 數(shù)組。聲明如下:
public abstract byte get();
public ByteBuffer get(byte[] dst, int offset, int length)
示例代碼如下:
newBuffer.get();
newBuffer.get(tmpByteArray, 0, 2);
經(jīng)過 gett 操作后, newBuffer 的 capacity、position、limit 的值如下圖所示:
3.5 ByteBuffer 的 rewind 方法
ByteBuffer 的 rewind 方法僅僅是將 position 設(shè)置為 0。
3.6 ByteBuffer 的 compat 方法
ByteBuffer 的 compat 方法將 position 所指向的、長度為 limit - position 的數(shù)據(jù)拷貝到數(shù)組的開頭,然后重新設(shè)定 position 和 limit 的值。compat 方法是非常有用的,比如你在解析讀取的報文的時候,如果消息不完整,你可以調(diào)用 compat 方法,然后繼續(xù)接收。
4. 總結(jié)
本小節(jié)主要是介紹了 ByteBuffer 的常見用法。要想熟練的從事 Java NIO 編程,首先必須理解 ByteBuffer 的原理和基本用法。ByteBuffer 的本質(zhì)就是維護(hù)了一個數(shù)組,通過 position、limit、capacity 來維護(hù)讀寫的位置。另外,網(wǎng)絡(luò)編程是需要關(guān)注字節(jié)序的,ByteBuffer 默認(rèn)是網(wǎng)絡(luò)字節(jié)序,你可以調(diào)用它的 order 方法設(shè)置字節(jié)序。