JavaNIO-Buffer

Buffer

java NIO庫是在jdk1.4中引入的,NIO與IO之間的第一個(gè)區(qū)別在于,IO是面向流的,而NIO是面向塊的。

所謂的面向流是指:系統(tǒng)一次一個(gè)字節(jié)的處理數(shù)據(jù),一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)的數(shù)據(jù),一個(gè)輸出流消費(fèi)一個(gè)字節(jié)的數(shù)據(jù)。

所謂的面向塊是指:以塊的形式處理數(shù)據(jù)。每一個(gè)操作都在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)塊。

按塊的方式處理數(shù)據(jù)要比按流的方式處理數(shù)據(jù)快,因?yàn)榘磯K的方式讀取或?qū)懭霐?shù)據(jù)所執(zhí)行的系統(tǒng)調(diào)用要遠(yuǎn)少于一次一個(gè)字節(jié)的方式,類似于BufferedInputStream的方式。

上面所說的塊,在NIO中就是Buffer對象。

一個(gè) Buffer(緩沖區(qū)) 實(shí)質(zhì)上是一個(gè)容器對象,它包含一些要寫入或者剛讀出的數(shù)據(jù)。在 NIO 庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的。在寫入數(shù)據(jù)時(shí),它是寫入到緩沖區(qū)中的。緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組。通常它是一個(gè)字節(jié)數(shù)組,但是也可以使用其他種類的數(shù)組。但是一個(gè)緩沖區(qū)不 僅僅 是一個(gè)數(shù)組。緩沖區(qū)提供了對數(shù)據(jù)的結(jié)構(gòu)化訪問,而且還可以跟蹤系統(tǒng)的讀/寫進(jìn)程。

舉例來說,ByteBuffer實(shí)質(zhì)上是對byte數(shù)組進(jìn)行了封裝,其內(nèi)部是一個(gè)byte數(shù)組,ByteBuffer對象提供了一些實(shí)用的API供我們?nèi)ゲ僮鬟@個(gè)數(shù)組,完成一些讀取或?qū)懭氲墓δ?。我們所要學(xué)習(xí)的,就是理解在調(diào)用這些API的時(shí)候,Buffer處理數(shù)組的方式。

除了boolean類型之外,java為每種基本類型都封裝了對應(yīng)的Buffer對象。

image.png

狀態(tài)變量

Buffer使用四個(gè)值指定了緩沖區(qū)在某個(gè)時(shí)刻的狀態(tài):

容量(Capacity):緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量

實(shí)際上,這個(gè)值指定了底層數(shù)組的大小。這一值在緩沖區(qū)創(chuàng)建時(shí)被設(shè)定,并且永遠(yuǎn)不能被改變。

位置(Position):下一個(gè)要被讀或?qū)懙脑氐乃饕?/p>

position 變量跟蹤已經(jīng)寫了多少數(shù)據(jù)。更準(zhǔn)確地說,它指定了下一個(gè)字節(jié)將放到數(shù)組的哪一個(gè)元素中。比如,從通道中讀三個(gè)字節(jié)到緩沖區(qū)中,那么緩沖區(qū)的 position 將會設(shè)置為3,指向數(shù)組中第四個(gè)元素。

初始的position值為0。

邊界(Limit):緩沖區(qū)的第一個(gè)不能被讀或?qū)懙脑??;蛘哒f,緩沖區(qū)中現(xiàn)存元素的計(jì)數(shù)。

在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。

當(dāng)切換Buffer到讀模式時(shí), limit表示你最多能讀到多少數(shù)據(jù)。因此,當(dāng)切換Buffer到讀模式時(shí),limit會被設(shè)置成寫模式下的position值。

標(biāo)記(Mark):一個(gè)備忘位置。

調(diào)用 mark()來設(shè)定 mark = postion。調(diào)用 reset()設(shè)定 position = mark。

初始的mark值為-1。

上面四個(gè)屬性遵循以下的關(guān)系:

0 <= mark <= position <= limit <= capacity

API

image.png
  • 創(chuàng)建

在了解這些api之前,首先需要知道如何創(chuàng)建一個(gè)Buffer對象:

在上一個(gè)小節(jié)中提到的7種緩沖區(qū)類沒有一種是可以直接實(shí)例化的,他們都是抽象類,但都包含了靜態(tài)工廠方法創(chuàng)建相應(yīng)的實(shí)例。以ByteBuffer為例:(對于其他六中緩沖區(qū)類也適用)

ByteBuffer buffer = ByteBuffer.allocate(1024);

allocate方法分配了一個(gè)具有指定大小底層數(shù)組的緩沖區(qū)對象,這個(gè)大小也就是上面提到的Capacity。

我們也可以使用已經(jīng)存在的數(shù)組來作為緩沖區(qū)對象的底層數(shù)組:

byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);

此時(shí),buffer對象的底層數(shù)組指向了array,這意味著直接修改array數(shù)組也會使buffer對象讀取的數(shù)據(jù)產(chǎn)生變化。

byte[] bs = new byte[10];
ByteBuffer buffer = ByteBuffer.wrap(bs);
System.out.println(buffer.toString());

打印如下:

java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

可見,新初始化的Buffer實(shí)例中,position = 0,limit=capacity=10

  • 存取

注意到Buffer類中并沒有提供get或者put函數(shù)。實(shí)際上每一個(gè)Buffer對象都有這兩個(gè)函數(shù),但它們所采用的參數(shù)類型,以及它們返回的數(shù)據(jù)類型,對每個(gè)子類來說都是唯一的,所以它們不能在頂層Buffer類中被抽象地聲明。這些存取方法被定義在Buffer類的子類當(dāng)中,我們一ByteBuffer為例:

public abstract byte get();
public abstract byte get (int index);
public abstract ByteBuffer put (byte b);
public abstract ByteBuffer put (int index, byte b);

ByteBuffer實(shí)際上還提供了 get(byte[] dst, int offset, int length)這樣的接口,其內(nèi)部實(shí)現(xiàn)也是循環(huán)調(diào)用了get()方法。

get和put可以是相對的或者是絕對的。

相對方案是不帶有索引參數(shù)的函數(shù)。當(dāng)相對函數(shù)被調(diào)用時(shí),位置在返回時(shí)前進(jìn)一。

絕對存取不會影響緩沖區(qū)的位置屬性(Position、Limit、Capacity、Mark)。

buffer.put((byte)'h').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
print(buffer, bs);
buffer.put(0, (byte)'y').put((byte)'y');
print(buffer, bs);

//觀察Buffer底層存儲情況
public static void print(Buffer buffer, byte[] bs) {
        System.out.println(buffer.toString());
        for (int i = 0; i < bs.length; i++) {
            if (bs[i] != 0) {
                char c = (char)bs[i];
                System.out.print(c);
            } else {
                System.out.print("$");
            }
        }
        System.out.println("");
    }

打印如下:

java.nio.HeapByteBuffer[pos=5 lim=10 cap=10]
hello$$$$$
java.nio.HeapByteBuffer[pos=6 lim=10 cap=10]
yelloy$$$$

可以看到,存入5個(gè)字節(jié)之后,position增加為5,limit與capacity不變。
調(diào)用buffer.put(0, (byte)'y'),將bs[0]的數(shù)據(jù)改寫為(byte)'y',position并沒有改變。

  • Buffer.flip()

我們想要將剛剛寫入的數(shù)據(jù)讀出的話應(yīng)該怎么做?應(yīng)該將position設(shè)為0:buffer.position(0),就可以從正確的位置開始獲取數(shù)據(jù)。但是它是怎樣知道何時(shí)到達(dá)我們所插入數(shù)據(jù)末端的呢?這就是邊界屬性被引入的目的。邊界屬性指明了緩沖區(qū)有效內(nèi)容的末端。我們需要將limit設(shè)置為當(dāng)前位置:buffer.limit(buffer.position())。

buffer.limit(buffer.position()).position(0);

Buffer已經(jīng)提供了一個(gè)方法封裝了這些操作:

public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
}
buffer.flip();
print(buffer, bs);

打印如下:

java.nio.HeapByteBuffer[pos=0 lim=6 cap=10]
yelloy$$$$

調(diào)用buffer.flip()后,limit設(shè)置為當(dāng)前position值,position重置為0.

  • Buffer.rewind()

緊接著上面的程序:

System.out.println((char)buffer.get());
System.out.println((char)buffer.get(3));
print(buffer, bs);
        
buffer.rewind();
print(buffer, bs);

打印如下:

y
l
java.nio.HeapByteBuffer[pos=1 lim=6 cap=10]
yelloy$$$$
java.nio.HeapByteBuffer[pos=0 lim=6 cap=10]
yelloy$$$$

可以看到,rewind()方法與filp()相似,但是不影響limit,他只是將position設(shè)為0,這樣就可以從新讀取已經(jīng)讀過的數(shù)據(jù)了。

public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
}
  • Buffer.mark()、Buffer.reset()
public final Buffer mark() {
        mark = position;
        return this;
}

public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
}

Buffer.mark(),使緩沖區(qū)能夠記住一個(gè)位置并在之后將其返回。

緩沖區(qū)的標(biāo)記在mark()函數(shù)被調(diào)用之前是未定義的,調(diào)用時(shí)標(biāo)記被設(shè)為當(dāng)前位置的值。reset()函數(shù)將位置設(shè)為當(dāng)前的標(biāo)記值。如果標(biāo)記值未定義,調(diào)用reset()將導(dǎo)致InvalidMarkException異常。

buffer.position(2);
buffer.mark();
print(buffer, bs);
buffer.position(4);
print(buffer, bs);
buffer.reset();
print(buffer, bs);

打印如下:

java.nio.HeapByteBuffer[pos=2 lim=6 cap=10]
yelloy$$$$
java.nio.HeapByteBuffer[pos=4 lim=6 cap=10]
yelloy$$$$
java.nio.HeapByteBuffer[pos=2 lim=6 cap=10]
yelloy$$$$
  • Buffer.remaining()、Buffer.hasRemaining()

remaining()函數(shù)將返回從當(dāng)前位置到上界還剩余的元素?cái)?shù)目。

hasRemaining()會返回是否已經(jīng)達(dá)到緩沖區(qū)的邊界。

public final int remaining() {
        return limit - position;
}

public final boolean hasRemaining() {
        return position < limit;
}

有兩種方法讀取緩沖區(qū)的所有剩余數(shù)據(jù):

// 第一種
for (int i = 0; buffer.hasRemaining(), i++) {
    myByteArray [i] = buffer.get();
}

// 第二種
int count = buffer.remaining();
for (int i = 0; i < count, i++) {
    myByteArray [i] = buffer.get();
}
  • Buffer.clear()

clear()函數(shù)將緩沖區(qū)重置為空狀態(tài)。它并不改變緩沖區(qū)中的任何數(shù)據(jù)元素,而是僅僅將上界設(shè)為容量的值,并把位置設(shè)回 0。

public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
}
  • ByteBuffer.compact()

compact()方法并不是Buffer接口中定義的,而是屬于ByteBuffer。

如果Buffer中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù),但是此時(shí)想要先寫些數(shù)據(jù),那么使用compact()方法。

compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設(shè)到最后一個(gè)未讀元素正后面。limit屬性依然像clear()方法一樣,設(shè)置成capacity?,F(xiàn)在Buffer準(zhǔn)備好寫數(shù)據(jù)了,但是不會覆蓋未讀的數(shù)據(jù)。

print(buffer, bs);
System.out.println(buffer.remaining());
buffer.compact();
print(buffer, bs);

打印如下:

java.nio.HeapByteBuffer[pos=2 lim=6 cap=10]
yelloy$$$$
4
java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
lloyoy$$$$
  • ByteBuffer.equals()、ByteBuffer.compareTo()

可以使用equals()和compareTo()方法兩個(gè)Buffer。

下面提到的剩余元素是從 position到limit之間的元素。

equals()

當(dāng)滿足下列條件時(shí),表示兩個(gè)Buffer相等:

有相同的類型(byte、char、int等)。

Buffer中剩余的byte、char等的個(gè)數(shù)相等。

Buffer中所有剩余的byte、char等都相同。

在每個(gè)緩沖區(qū)中應(yīng)被get()函數(shù)返回的剩余數(shù)據(jù)元素序列必須一致。

equals只是比較Buffer的一部分,不是每一個(gè)在它里面的元素都比較。實(shí)際上,它只比較Buffer中的剩余元素。

compareTo()

compareTo()方法比較兩個(gè)Buffer的剩余元素(byte、char等), 如果滿足下列條件,則認(rèn)為一個(gè)Buffer“小于”另一個(gè)Buffer:

第一個(gè)不相等的元素小于另一個(gè)Buffer中對應(yīng)的元素 。

所有元素都相等,但第一個(gè)Buffer比另一個(gè)先耗盡(第一個(gè)Buffer的元素個(gè)數(shù)比另一個(gè)少)。

只讀緩沖區(qū)

可以使用asReadOnlyBuffer()函數(shù)來生成一個(gè)只讀的緩沖區(qū)視圖。

這個(gè)新的緩沖區(qū)不允許使用put(),并且其isReadOnly()函數(shù)將會返回true。對這一只讀緩沖區(qū)的put()函數(shù)的調(diào)用嘗試會導(dǎo)致拋出ReadOnlyBufferException異常。

兩個(gè)緩沖區(qū)共享數(shù)據(jù)元素,擁有同樣的容量,但每個(gè)緩沖區(qū)擁有各自的位置,上界和標(biāo)記屬性。對一個(gè)緩沖區(qū)內(nèi)的數(shù)據(jù)元素所做的改變會反映在另外一個(gè)緩沖區(qū)上。

復(fù)制緩沖區(qū)

duplicate()函數(shù)創(chuàng)建了一個(gè)與原始緩沖區(qū)相似的新緩沖區(qū)。兩個(gè)緩沖區(qū)共享數(shù)據(jù)元素,擁有同樣的容量,但每個(gè)緩沖區(qū)擁有各自的位置,上界和標(biāo)記屬性。對一個(gè)緩沖區(qū)內(nèi)的數(shù)據(jù)元素所做的改變會反映在另外一個(gè)緩沖區(qū)上。這一副本緩沖區(qū)具有與原始緩沖區(qū)同樣的數(shù)據(jù)視圖。如果原始的緩沖區(qū)為只讀,或者為直接緩沖區(qū),新的緩沖區(qū)將繼承這些屬性。

復(fù)制一個(gè)緩沖區(qū)會創(chuàng)建一個(gè)新的Buffer對象,但并不復(fù)制數(shù)據(jù)。原始緩沖區(qū)和副本都會操作同樣的數(shù)據(jù)元素。

直接緩沖區(qū)

直接ByteBuffer是通過調(diào)用ByteBuffer.allocateDirect(int capacity)函數(shù)來創(chuàng)建的。

什么是直接緩沖區(qū)(DirectByteBuffer)呢?直接緩沖區(qū)意味著所分配的這段內(nèi)存是堆外內(nèi)存,而我們通過ByteBuffer.allocate(int capacity)或者ByteBuffer.wrap(byte[] array)分配的內(nèi)存是堆內(nèi)存,其返回的實(shí)例為HeapByteBuffer,HeapByteBuffer中持有一個(gè)byte數(shù)組,這個(gè)數(shù)組所占有的內(nèi)存是堆內(nèi)內(nèi)存。

Netty之Java堆外內(nèi)存掃盲貼了解java堆外內(nèi)存。

sun.nio.ch.FileChannelImpl.read(ByteBuffer dst)

image.png

sun.nio.ch.IOUtil.read(FileDescriptor fd, ByteBuffer dst, long position,NativeDispatcher nd, Object lock)

image.png

觀察上面兩段代碼發(fā)現(xiàn),我們通過一個(gè)文件通道去填充一個(gè)ByteBuffer時(shí),先執(zhí)行sun.nio.ch.FileChannelImpl.read(ByteBuffer dst)方法,其中調(diào)用了sun.nio.ch.IOUtil.read(FileDescriptor fd, ByteBuffer dst, long position,NativeDispatcher nd, Object lock)方法,觀察這個(gè)方法,發(fā)現(xiàn)其中會做一個(gè)判斷:如果是直接緩沖區(qū)(DirectBuffer),直接調(diào)用readIntoNativeBuffer(fd, dst, position, nd, lock)并返回;如果是非直接緩沖區(qū)(HeapByteBuffer),先獲取一個(gè)直接緩沖區(qū),然后使用該直接緩沖區(qū)作為參數(shù)調(diào)用readIntoNativeBuffer(fd, dst, position, nd, lock),然后將填充完畢的DirectBuffer的內(nèi)容復(fù)制到HeapByteBuffer當(dāng)中,然后返回。

直接緩沖區(qū)的內(nèi)存分配調(diào)用了sun.misc.Unsafe.allocateMemory(size),返回了內(nèi)存基地址,實(shí)際上就是malloc。

看一下java doc對DirectBuffer的說明:

A byte buffer is either direct or non-direct. Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations.

給定一個(gè)直接字節(jié)緩沖區(qū),Java 虛擬機(jī)將盡最大努力直接對它執(zhí)行本機(jī) I/O 操作。也就是說,它會在每一次調(diào)用底層操作系統(tǒng)的本機(jī) I/O 操作之前(或之后),嘗試避免將緩沖區(qū)的內(nèi)容拷貝到一個(gè)中間緩沖區(qū)中(或者從一個(gè)中間緩沖區(qū)中拷貝數(shù)據(jù))。

結(jié)合上面的代碼,就可以理解這段話的含義。

那么,為什么需要直接緩沖區(qū),也就是堆外內(nèi)存來執(zhí)行IO呢?

以讀操作為例,數(shù)據(jù)從底層硬件讀到內(nèi)核緩沖區(qū)之后,操作系統(tǒng)會從內(nèi)核空間復(fù)制數(shù)據(jù)到用戶空間,此時(shí)的用戶進(jìn)程空間就是jvm,這意味著 I/O 操作的目標(biāo)內(nèi)存區(qū)域必須是連續(xù)的字節(jié)序列。在 Java 中,數(shù)組是對象,在 JVM 中,字節(jié)數(shù)組可能不會在內(nèi)存中連續(xù)存儲。因此,這個(gè)連續(xù)的字節(jié)序列就是直接緩沖區(qū)中分配的內(nèi)存空間。需要直接緩沖區(qū)來當(dāng)一個(gè)中間人,完成數(shù)據(jù)的寫入或者讀取。

其實(shí),在傳統(tǒng)BIO中,也是這么做的,同樣需要一個(gè)堆外內(nèi)存來充當(dāng)這個(gè)中間人:比如FileInputStream.read(byte b[], int off, int len):

FileInputStream.read(byte b[], int off, int len)調(diào)用了readBytes(byte b[], int off, int len)方法,這個(gè)方法是一個(gè)本地方法:

JNIEXPORT jint JNICALL  
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,  
        jbyteArray bytes, jint off, jint len) {//除了前兩個(gè)參數(shù),后三個(gè)就是readBytes方法傳遞進(jìn)來的,字節(jié)數(shù)組、起始位置、長度三個(gè)參數(shù)  
return readBytes(env, this, bytes, off, len, fis_fd);  
}  
jint
readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
          jint off, jint len, jfieldID fid)
{
    jint nread;
    char stackBuf[BUF_SIZE];
    char *buf = NULL;
    FD fd;
 
    if (IS_NULL(bytes)) {
        JNU_ThrowNullPointerException(env, NULL);
        return -1;
    }
 
    if (outOfBounds(env, off, len, bytes)) {
        JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
        return -1;
    }
 
    if (len == 0) {
        return 0;
    } else if (len > BUF_SIZE) {
        buf = malloc(len);// buf的分配
        if (buf == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return 0;
        }
    } else {
        buf = stackBuf;
    }
 
    fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        nread = -1;
    } else {
        nread = IO_Read(fd, buf, len);// buf是使用malloc分配的直接緩沖區(qū),也就是堆外內(nèi)存
        if (nread > 0) {
            (*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);// 將直接緩沖區(qū)的內(nèi)容copy到bytes數(shù)組中
        } else if (nread == JVM_IO_ERR) {
            JNU_ThrowIOExceptionWithLastError(env, "Read error");
        } else if (nread == JVM_IO_INTR) {
            JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);
        } else { /* EOF */
            nread = -1;
        }
    }
 
    if (buf != stackBuf) {
        free(buf);
    }
    return nread;
}

可以看到,這個(gè)方法其實(shí)最關(guān)鍵的就是IO_Read這個(gè)宏定義的處理,而IO_Read其實(shí)只是代表了一個(gè)方法名稱叫handleRead,我們?nèi)タ匆幌耯andleRead的源碼。

JNIEXPORT  
size_t  
handleRead(jlong fd, void *buf, jint len)  
{  
    DWORD read = 0;  
    BOOL result = 0;  
    HANDLE h = (HANDLE)fd;  
    if (h == INVALID_HANDLE_VALUE) {
        return -1;  
    }  
    result = ReadFile(h,          
                      buf,       
                      len,       
                      &read,      
                      NULL);     
    if (result == 0) {
        int error = GetLastError();  
        if (error == ERROR_BROKEN_PIPE) {  
            return 0; 
        }  
        return -1;  
    }  
    return read;  
}  

通過上面的代碼可以發(fā)現(xiàn),傳統(tǒng)的BIO也是把操作系統(tǒng)返回的數(shù)據(jù)放到直接緩沖區(qū)當(dāng)中,然后在copy回我們傳入的byte數(shù)組當(dāng)中。

所有的緩沖區(qū)都提供了一個(gè)叫做isDirect()的boolean函數(shù),來測試特定緩沖區(qū)是否為直接緩沖區(qū)。

A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.

直接緩沖區(qū)雖然避免了復(fù)制內(nèi)存帶來的消耗,但直接緩沖區(qū)使用的內(nèi)存是通過調(diào)用本地操作系統(tǒng)方面的代碼分配的,繞過了標(biāo)準(zhǔn) JVM 堆棧。建立和銷毀直接緩沖區(qū)會明顯比具有堆棧的緩沖區(qū)更加破費(fèi),并且可能帶來不易察覺的內(nèi)存泄漏,或oom問題。所以,如果對于性能要求不是很嚴(yán)格,一般情況下,使用非直接緩沖區(qū)就足夠了。

緩沖區(qū)分片

slice() 方法根據(jù)現(xiàn)有的緩沖區(qū)創(chuàng)建一種 子緩沖區(qū) 。也就是說,它創(chuàng)建一個(gè)新的緩沖區(qū),新緩沖區(qū)與原來的緩沖區(qū)的一部分共享數(shù)據(jù)。

現(xiàn)在我們對這個(gè)緩沖區(qū) 分片 ,以創(chuàng)建一個(gè)包含槽 3 到槽 6 的子緩沖區(qū)。在某種意義上,子緩沖區(qū)就像原來的緩沖區(qū)中的一個(gè) 窗口。

窗口的起始和結(jié)束位置通過設(shè)置 position 和 limit 值來指定,然后調(diào)用 Buffer 的 slice() 方法:

print(buffer, bs);
buffer.position( 3 ).limit( 7 );
ByteBuffer slice = buffer.slice();
print(slice, slice.array());

打印如下:

java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
lloyoy$$$$
java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]
lloyoy$$$$

slice 是緩沖區(qū)的 子緩沖區(qū) 。不過, slice 和 buffer 共享同一個(gè)底層數(shù)據(jù)數(shù)組。

類型視圖緩沖區(qū)

我們知道,Buffer可以作為通道執(zhí)行IO的源頭或者目標(biāo),但是通道只接受ByteBuffer類型的參數(shù)。比如read(ByteBuffer dst)。

我們在進(jìn)行IO操作時(shí),可能會使用各種ByteBuffer類去讀取文件內(nèi)容,接收來自網(wǎng)絡(luò)連接的數(shù)據(jù)使用各種ByteBuffer類去讀取文件內(nèi)容,接收來自網(wǎng)絡(luò)連接的數(shù)據(jù)等。一旦數(shù)據(jù)到達(dá)了您的 ByteBuffer,我們需要對他進(jìn)行一些操作。ByteBuffer類允許創(chuàng)建視圖來將byte型緩沖區(qū)字節(jié)數(shù)據(jù)映射為其它的原始數(shù)據(jù)類型。例如,asLongBuffer()函數(shù)創(chuàng)建一個(gè)將八個(gè)字節(jié)型數(shù)據(jù)當(dāng)成一個(gè) long 型數(shù)據(jù)來存取的視圖緩沖區(qū)。

public abstract class ByteBuffer extends Buffer implements Comparable{
    // 這里僅列出部分API
    public abstract CharBuffer asCharBuffer();
    public abstract ShortBuffer asShortBuffer();
    public abstract IntBuffer asIntBuffer();
    public abstract LongBuffer asLongBuffer();
    public abstract FloatBuffer asFloatBuffer();
    public abstract DoubleBuffer asDoubleBuffer();
}
buffer.clear();
buffer.order(ByteOrder.BIG_ENDIAN);//指定字節(jié)序
buffer.put (0, (byte)0);
buffer.put (1, (byte)'H');
buffer.put (2, (byte)0);
buffer.put (3, (byte)'i');
buffer.put (4, (byte)0);
buffer.put (5, (byte)'!');
buffer.put (6, (byte)0);

CharBuffer charBuffer = buffer.asCharBuffer();
System.out.println("pos=" + charBuffer.position() + " limit=" + charBuffer.limit() + " cap=" + charBuffer.capacity());;
print(charBuffer, bs);

打印如下:

pos=0 limit=5 cap=5
Hi!   
$H$i$!$$$$

新的緩沖區(qū)的容量是字節(jié)緩沖區(qū)中存在的元素?cái)?shù)量除以視圖類型中組成一個(gè)數(shù)據(jù)類型的字節(jié)數(shù)。視圖緩沖區(qū)的第一個(gè)元素從創(chuàng)建它的ByteBuffer對象的位置開始(positon()函數(shù)的返回值)。

image.png

測試代碼

前面提到的測試代碼匯總:

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;

public class Test {
    public static void main(String[] args) {
        byte[] bs = new byte[10];
        ByteBuffer buffer = ByteBuffer.wrap(bs);
        System.out.println(buffer.toString());
        // put
        buffer.put((byte)'h').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
        print(buffer, bs);
        buffer.put(0, (byte)'y').put((byte)'y');
        print(buffer, bs);
        
        //flip
        buffer.flip();
        print(buffer, bs);
        
        // rewind
        System.out.println((char)buffer.get());
        System.out.println((char)buffer.get(3));
        print(buffer, bs);
        
        buffer.rewind();
        print(buffer, bs);
        
        // mark reset
        buffer.position(2);
        buffer.mark();
        print(buffer, bs);
        buffer.position(4);
        print(buffer, bs);
        buffer.reset();
        print(buffer, bs);
        
        // compact
        System.out.println(buffer.remaining());
        buffer.compact();
        print(buffer, bs);
        
        // slice
        buffer.position( 3 ).limit( 7 );
        ByteBuffer slice = buffer.slice();
        print(slice, slice.array());
        
        // asCharBuffer
        buffer.clear();
        buffer.order(ByteOrder.BIG_ENDIAN);
        buffer.put (0, (byte)0);
        buffer.put (1, (byte)'H');
        buffer.put (2, (byte)0);
        buffer.put (3, (byte)'i');
        buffer.put (4, (byte)0);
        buffer.put (5, (byte)'!');
        buffer.put (6, (byte)0);
        CharBuffer charBuffer = buffer.asCharBuffer();
        System.out.println("pos=" + charBuffer.position() + " limit=" + charBuffer.limit() + " cap=" + charBuffer.capacity());;
        print(charBuffer, bs);
    }
    
    public static void print(Buffer buffer, byte[] bs) {
        System.out.println(buffer.toString());
        for (int i = 0; i < bs.length; i++) {
            if (bs[i] != 0) {
                char c = (char)bs[i];
                System.out.print(c);
            } else {
                System.out.print("$");
            }
        }
        System.out.println("");
    }
}

參考

Why is Traditional Java I/O Uninterruptable?

Java NIO Buffer

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,482評論 0 22
  • Java NIO(New IO)是從Java 1.4版本開始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,925評論 1 143
  • java nio Java的IO體系:舊IO新IO:nio,用ByteBuffer和FileChannel讀寫ni...
    則不達(dá)閱讀 913評論 0 2
  • 從小到大,最大的愛好就是讀書。最初的起蒙老師是我爸爸,小時(shí)候經(jīng)常停電,每次媽媽含點(diǎn)上煤油燈照亮。有時(shí)候爸...
    3b43e72b4809閱讀 189評論 0 1
  • 我躺在家里的竹席上,隨即起身背靠一扇半開的窗戶,窗戶后面是金色與綠色層次分明的麥浪,連接天際的無限深藍(lán)以及點(diǎn)綴其中...
    年輕10斤閱讀 341評論 0 2

友情鏈接更多精彩內(nèi)容