Java NIO 之 Buffer(緩沖區)


一 Buffer(緩沖區)介紹

Java NIO Buffers用於和NIO Channel交互。 我們從Channel中讀取數據到buffers里,從Buffer把數據寫入到Channels.

Buffer本質上就是一塊內存區,可以用來寫入數據,並在稍后讀取出來。這塊內存被NIO Buffer包裹起來,對外提供一系列的讀寫方便開發的接口。

在Java NIO中使用的核心緩沖區如下(覆蓋了通過I/O發送的基本數據類型:byte, char、short, int, long, float, double ,long):

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

Java NIO中使用的核心緩沖區

利用Buffer讀寫數據,通常遵循四個步驟:

  1. 把數據寫入buffer;
  2. 調用flip;
  3. 從Buffer中讀取數據;
  4. 調用buffer.clear()或者buffer.compact()。

當寫入數據到buffer中時,buffer會記錄已經寫入的數據大小。當需要讀數據時,通過 flip() 方法把buffer從寫模式調整為讀模式;在讀模式下,可以讀取所有已經寫入的數據。

當讀取完數據后,需要清空buffer,以滿足后續寫入操作。清空buffer有兩種方式:調用 clear()compact() 方法。clear會清空整個buffer,compact則只清空已讀取的數據,未被讀取的數據會被移動到buffer的開始位置,寫入位置則近跟着未讀數據之后。

Buffer的容量,位置,上限(Buffer Capacity, Position and Limit)

Buffer緩沖區實質上就是一塊內存,用於寫入數據,也供后續再次讀取數據。這塊內存被NIO Buffer管理,並提供一系列的方法用於更簡單的操作這塊內存。

一個Buffer有三個屬性是必須掌握的,分別是:

  • capacity容量
  • position位置
  • limit限制

position和limit的具體含義取決於當前buffer的模式。capacity在兩種模式下都表示容量。

下面有張示例圖,描訴了讀寫模式下position和limit的含義:

不同模式下position和limit的含義

容量(Capacity)

作為一塊內存,buffer有一個固定的大小,叫做capacit(容量)。也就是最多只能寫入容量值得字節,整形等數據。一旦buffer寫滿了就需要清空已讀數據以便下次繼續寫入新的數據。

位置(Position)

當寫入數據到Buffer的時候需要從一個確定的位置開始,默認初始化時這個位置position為0,一旦寫入了數據比如一個字節,整形數據,那么position的值就會指向數據之后的一個單元,position最大可以到capacity-1.

當從Buffer讀取數據時,也需要從一個確定的位置開始。buffer從寫入模式變為讀取模式時,position會歸零,每次讀取后,position向后移動。

上限(Limit)

在寫模式,limit的含義是我們所能寫入的最大數據量,它等同於buffer的容量。

一旦切換到讀模式,limit則代表我們所能讀取的最大數據量,他的值等同於寫模式下position的位置。換句話說,您可以讀取與寫入數量相同的字節數(限制設置為寫入的字節數,由位置標記)。

二 Buffer的常見方法

方法 介紹
abstract Object array() 返回支持此緩沖區的數組 (可選操作)
abstract int arrayOffset() 返回該緩沖區的緩沖區的第一個元素的背襯數組中的偏移量 (可選操作)
int capacity() 返回此緩沖區的容量
Buffer clear() 清除此緩存區。將position = 0;limit = capacity;mark = -1;
Buffer flip() flip()方法可以吧Buffer從寫模式切換到讀模式。調用flip方法會把position歸零,並設置limit為之前的position的值。 也就是說,現在position代表的是讀取位置,limit標示的是已寫入的數據位置。
abstract boolean hasArray() 告訴這個緩沖區是否由可訪問的數組支持
boolean hasRemaining() return position < limit,返回是否還有未讀內容
abstract boolean isDirect() 判斷個緩沖區是否為 direct
abstract boolean isReadOnly() 判斷告知這個緩沖區是否是只讀的
int limit() 返回此緩沖區的限制
Buffer position(int newPosition) 設置這個緩沖區的位置
int remaining() return limit - position; 返回limit和position之間相對位置差
Buffer rewind() 把position設為0,mark設為-1,不改變limit的值
Buffer mark() 將此緩沖區的標記設置在其位置

三 Buffer的使用方式/方法介紹

分配緩沖區(Allocating a Buffer)

為了獲得緩沖區對象,我們必須首先分配一個緩沖區。在每個Buffer類中,allocate()方法用於分配緩沖區。

下面來看看ByteBuffer分配容量為28字節的例子:

ByteBuffer buf = ByteBuffer.allocate(28);

下面來看看另一個示例:CharBuffer分配空間大小為2048個字符。

CharBuffer buf = CharBuffer.allocate(2048);

寫入數據到緩沖區(Writing Data to a Buffer)

寫數據到Buffer有兩種方法:

  • 從Channel中寫數據到Buffer
  • 手動寫數據到Buffer,調用put方法

下面是一個實例,演示從Channel寫數據到Buffer:

 int bytesRead = inChannel.read(buf); //read into buffer.

通過put寫數據:

buf.put(127);

put方法有很多不同版本,對應不同的寫數據方法。例如把數據寫到特定的位置,或者把一個字節數據寫入buffer。看考JavaDoc文檔可以查閱的更多數據。

翻轉(flip())

flip()方法可以吧Buffer從寫模式切換到讀模式。調用flip方法會把position歸零,並設置limit為之前的position的值。 也就是說,現在position代表的是讀取位置,limit標示的是已寫入的數據位置。

從Buffer讀取數據(Reading Data from a Buffer)

從Buffer讀數據也有兩種方式。

  • 從buffer讀數據到channel
  • 從buffer直接讀取數據,調用get方法

讀取數據到channel的例子:

int bytesWritten = inChannel.write(buf);

調用get讀取數據的例子:

byte aByte = buf.get();

get也有諸多版本,對應了不同的讀取方式。

rewind()

Buffer.rewind()方法將position置為0,這樣我們可以重復讀取buffer中的數據。limit保持不變。

clear() and compact()

一旦我們從buffer中讀取完數據,需要復用buffer為下次寫數據做准備。只需要調用clear()或compact()方法。

如果調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數據並未清除,只是這些標記告訴我們可以從哪里開始往Buffer里寫數據。

如果Buffer還有一些數據沒有讀取完,調用clear就會導致這部分數據被“遺忘”,因為我們沒有標記這部分數據未讀。

針對這種情況,如果需要保留未讀數據,那么可以使用compact。 因此 compact()clear() 的區別就在於: 對未讀數據的處理,是保留這部分數據還是一起清空

mark()與reset()方法

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

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset();  //set position back to mark.    

equals() and compareTo()

可以用eqauls和compareTo比較兩個buffer

equals():

判斷兩個buffer相對,需滿足:

  • 類型相同
  • buffer中剩余字節數相同
  • 所有剩余字節相等

從上面的三個條件可以看出,equals只比較buffer中的部分內容,並不會去比較每一個元素。

compareTo():

compareTo也是比較buffer中的剩余元素,只不過這個方法適用於比較排序的:

四 Buffer常用方法測試

這里以ByteBuffer為例子說明抽象類Buffer的實現類的一些常見方法的使用:

package channel;

import java.nio.ByteBuffer;

public class ByteBufferMethods {
    public static void main(String args[]){
        //分配緩沖區(Allocating a Buffer)
        ByteBuffer buffer = ByteBuffer.allocate(33);

        System.out.println("-------------Test reset-------------");
        //clear()方法,position將被設回0,limit被設置成 capacity的值
        buffer.clear();
       // 設置這個緩沖區的位置
        buffer.position(5);
        //將此緩沖區的標記設置在其位置。沒有buffer.mark();這句話會報錯
        buffer.mark();
        buffer.position(10);
        System.out.println("before reset:      " + buffer);
        //將此緩沖區的位置重置為先前標記的位置。(buffer.position(5))
        buffer.reset();
        System.out.println("after reset:       " + buffer);

        System.out.println("-------------Test rewind-------------");
        buffer.clear();
        buffer.position(10);
        //返回此緩沖區的限制。
        buffer.limit(15);
        System.out.println("before rewind:       " + buffer);
        //把position設為0,mark設為-1,不改變limit的值
        buffer.rewind();
        System.out.println("before rewind:       " + buffer);

        System.out.println("-------------Test compact-------------");
        buffer.clear();
        buffer.put("abcd".getBytes());
        System.out.println("before compact:       " + buffer);
        System.out.println(new String(buffer.array()));
        //limit = position;position = 0;mark = -1; 翻轉,也就是讓flip之后的position到limit這塊區域變成之前的0到position這塊,
        //翻轉就是將一個處於存數據狀態的緩沖區變為一個處於准備取數據的狀態
        buffer.flip();
        System.out.println("after flip:       " + buffer);
        //get()方法:相對讀,從position位置讀取一個byte,並將position+1,為下次讀寫作准備
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println("after three gets:       " + buffer);
        System.out.println("\t" + new String(buffer.array()));
        //把從position到limit中的內容移到0到limit-position的區域內,position和limit的取值也分別變成limit-position、capacity。
        // 如果先將positon設置到limit,再compact,那么相當於clear()
        buffer.compact();
        System.out.println("after compact:       " + buffer);
        System.out.println("\t" + new String(buffer.array()));

        System.out.println("-------------Test get-------------");
        buffer = ByteBuffer.allocate(32);
        buffer.put((byte) 'a').put((byte) 'b').put((byte) 'c').put((byte) 'd')
                .put((byte) 'e').put((byte) 'f');
        System.out.println("before flip():       " + buffer);
        // 轉換為讀取模式
        buffer.flip();
        System.out.println("before get():       " + buffer);
        System.out.println((char) buffer.get());
        System.out.println("after get():       " + buffer);
        // get(index)不影響position的值
        System.out.println((char) buffer.get(2));
        System.out.println("after get(index):       " + buffer);
        byte[] dst = new byte[10];
        buffer.get(dst, 0, 2);
        System.out.println("after get(dst, 0, 2):       " + buffer);
        System.out.println("\t dst:" + new String(dst));
        System.out.println("buffer now is:       " + buffer);
        System.out.println("\t" + new String(buffer.array()));

        System.out.println("-------------Test put-------------");
        ByteBuffer bb = ByteBuffer.allocate(32);
        System.out.println("before put(byte):       " + bb);
        System.out.println("after put(byte):       " + bb.put((byte) 'z'));
        System.out.println("\t" + bb.put(2, (byte) 'c'));
        // put(2,(byte) 'c')不改變position的位置
        System.out.println("after put(2,(byte) 'c'):       " + bb);
        System.out.println("\t" + new String(bb.array()));
        // 這里的buffer是 abcdef[pos=3 lim=6 cap=32]
        bb.put(buffer);
        System.out.println("after put(buffer):       " + bb);
        System.out.println("\t" + new String(bb.array()));
    }
}

如果大家想搭建個人博客(一般使用的是第三方WordPress搭建或者你也可以使用Tale等開源博客系統搭建,非常方便)或者說使用redis數據庫、負載均衡等等第三方服務的話,推薦大家使用阿里雲,客觀角度來講,阿里雲的服務與質量都是最好的,而且學生優惠特別大,一年一下也就100多塊錢。這里是我的優惠券地址(我本人使用的是輕量級服務器):優惠券地址

參考:

官方JDK相關文檔

谷歌搜索排名第一的Java NIO教程

《Java程序員修煉之道》

ByteBuffer常用方法詳解

Java NIO 易百教程

歡迎關注我的微信公眾號:"Java面試通關手冊"(一個有溫度的微信公眾號,期待與你共同進步~~~堅持原創,分享美文,分享各種Java學習資源):


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM