Java NIO (一) 緩存區(Buffers)

前言

最近在學習java nio,但是苦於沒有找到中文資料,所以下決心計劃將資料《Java I-O, NIO and NIO.2》中的NIO和NIO.2兩部分翻譯成中文,自己水平有限,如有不當之處請看客指出,我會努力修改。附上此文檔下載地址:http://download.csdn.net/download/hongguo_cheng/10051014

NIO基於Buffer,Buffer中的內容被髮送到I/O或從I/O接收,通過Channels提供服務。本章介紹NIO的Buffer相關的類。

Buffers 介紹

buffer是存儲要發送到I/O服務或從I/O服務(用於執行輸入/輸出的操作系統組件)的固定數據量的對象。它位於應用程序和將緩衝數據寫入服務或從服務讀取數據並將其存入緩衝區的channel之間。

Buffers處理以下四個屬性:

  • Capacity:buffer可以存儲的數據對象的總數。這個capacity是在buffer創建時指定並且後續不允許修改。
  • Limit:不應該被讀取或寫入的第一個元素的從零開始的索引。 換句話說,它標識buffer中“實時”數據項的數量。
  • Position:可以讀取的下一個數據項的從零開始的索引或數據項可被寫入的位置。
  • Mark:當調用buffer的reset()方法時,buffer的position會重置到從零開始的索引,mark在初始化時未定義。

這四個屬性的關係如下: 0 <= mark <= position <= limit <= capacity

圖1-1 說明一個新創建的和麪向字節的緩衝區,capacity爲7。
buffer的容量爲7的示意圖

上圖說明:面向字節的buffer的邏輯佈局包含一個未定義的mark,當前的position,limit和capacity。

圖1-1 上面的buffer可以存儲最大數量爲7的元素。mark初始時未定義,position初始時設置爲0,limit初始時設置爲與capacity一致(7),capacity指定了這個buffer可以存儲的最大數量的字節數。你可以訪問從0-6的positions,Position 7位於buffer之外了。

Buffer 類以及其子類

Buffers由派生自抽象類java.nio.Buffer的類實現,下面描述了Buffer的方法

  • Object array() :返回支持buffer的數組
  • int arrayOffset() : 返回buffer內的第一個元素的偏移量
  • int capacity() :返回buffer的capacity
  • Buffer clear() :清除此buffer,將position設爲0,limit設爲capacity,mark被清除,該方法不會清除buffer中的數據,而是將其命名爲clear的確因爲它最常用於情況也是如此。
  • Buffer flip() :翻轉此buffer,limit設爲當前的position,position設爲0,當mark被定義,它將被丟棄。
  • boolean hasArray() :當此buffer是由數組支持和不能是隻讀時返回true,否則返回false。當此方法返回true,可以安全的調用array()和arrayOffset()方法。
  • boolean isDirect() :當此buffer是直接字節buffer(稍後章節會討論)時返回true,否則返回false。
  • boolean isReadOnly() : 當此buffer是隻讀時返回true,否則返回false。
  • int limit() : 返回buffer的limit。
  • Buffer limit(int newLimit) : 設置此buffer的limit爲newLimit。當position比newLimit大時,position會被設置爲newLimit。當mark被定義以及比newLimit大時,mark將被丟棄。當newLimit是負數或者比capacity大時會拋java.lang.IllegalArgumentException異常。否則返回此buffer對象。
  • Buffer mark() :設置buffer的mark到此position上然後返回此buffer對象。
  • int position() :返回buffer的position值。
  • Buffer position(int newPosition):設置buffer的position爲newPosition。當mark被定義以及比newPosition大時,mark會被丟棄。當newPosition是負數或者比limit大時會拋java.lang.IllegalArgumentException異常。否則返回此buffer對象。
  • int remaining() : 返回buffer中位置從position到limit的元素個數。
  • Buffer reset() :重置此buffer的position到之前mark的位置。調用此方法既不會改變mark的值也不會丟棄mark的值。當mark未設置時調用此方法會拋java.nio.InvalidMarkException異常。不然此方法返回此buffer對象。
  • Buffer rewind() : 倒回然後返回此buffer,buffer的position會設置爲0和mark被丟棄。

以上顯示了許多Buffer的方法中返回Buffer引用,以便可以將實例方法調用鏈接在一起。例如,不是指定的以下三行:

buf.mark();
buf.position(2);
buf.reset();

你可以更加方便的指定下面的一行

buf.mark().position(2).reset();

上面還顯示了全部的buffer可以讀取但不是所有的buffer可以寫入-例如,一個基於內存映射文件的buffer就只能時只讀的。你禁止向只讀buffer寫入;否則會拋ReadOnlyBufferException異常。在嘗試寫入該Buffer之前不確定此Buffer是否可寫時,調用isReadOnly()方法判斷是否只讀。

警告 Buffers是線程不安全的。你想用多線程訪問一個buffer時必須使用同步。

java.nio包中包含幾個擴展Buffer的抽象類,除了Boolean之外,每個基本類型都有對應的一個抽象類:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer 和 ShortBuffer。更進一步講,此包還包含了MappedByteBuffer作爲ByteBuffer的一個子類。

注意 操作系統執行面向字節的I/O,使用ByteBuffer去創建面向字節的buffer,用於存儲要寫入到目的地或從源讀取的字節。其他基本數據類型的buffer 類使你創建多字節的view buffers(稍後討論)所以你可以從概念上講去執行字符型,雙精度浮點型,32位整型等等的I/O操作。然而,I/O的操作是以字節流的形式在操作。

下面的示例說明ByteBuffer類中的capacity,limit,position以及剩餘元素。

package com.nio.demo;

import java.nio.Buffer;
import java.nio.ByteBuffer;

public class ByteBufferDemo {
    public static void main(String[] args) {
        Buffer buffer = ByteBuffer.allocate(7);
        System.out.println("Capacity: " + buffer.capacity());
        System.out.println("Limit: " + buffer.limit());
        System.out.println("Position: " + buffer.position());
        System.out.println("Remaining: " + buffer.remaining());
        System.out.println("Changing buffer limit to 5");
        buffer.limit(5);
        System.out.println("Limit: " + buffer.limit());
        System.out.println("Position: " + buffer.position());
        System.out.println("Remaining: " + buffer.remaining());
        System.out.println("Changing buffer position to 3");
        buffer.position(3);
        System.out.println("Position: " + buffer.position());
        System.out.println("Remaining: " + buffer.remaining());
        System.out.println(buffer);
    }
}

上面的代碼中的main()方法首先需要獲取一個buffer對象,Buffer類是抽象不能實例化,所以使用ByteBuffer類和它的allocate()實例方法去分配包含7個字節的buffer,然後main方法分類的調用Buffer的方法去演示capacity,limit,position和剩餘元素。

上述代碼執行結果:

Capacity: 7
Limit: 7
Position: 0
Mark:java.nio.HeapByteBuffer[pos=0 lim=7 cap=7]
Remaining: 7
Changing buffer limit to 5
Limit: 5
Position: 0
Remaining: 5
Changing buffer position to 3
Position: 3
Remaining: 2
java.nio.HeapByteBuffer[pos=3 lim=5 cap=7]

最後輸出一行顯示分配給buffer的ByteBuffer實例實際上是java.nio.HeapByteBuffer類的一個實例。

深入理解Buffers

前面討論的Buffer類已經給了你一些關於NIO buffers的大體情況。然而,還有更多的要去探索研究。這部分講解會讓你從buffer的創建,buffer的讀和寫,buffer的翻轉,buffer的mark,Buffer子類的操作,字節排序和直接buffers等幾個方面更加深入的瞭解buffers。

注意 雖然基本數據類型的buffer類有相似的能力,但是ByteBuffer是最大和最通用的。畢竟,字節是操作系統傳輸數據項使用的基本單元。因此我會使用ByteBuffer來演示大部分buffer的操作。我也會使用CharBuffer去增加多樣性操作。

Buffer的創建

ByteBuffer和其他基本數據類型buffer類聲明瞭多個類方法去創建一個對應類型的buffer對象。例如,ByteBuffer聲明瞭一下類方法去創建ByteBuffer實例:

  • ByteBuffer allocate(int capacity):分配一個擁有capacity大小的字節buffer對象。此buffer的position是0,limit就是它的capacity,mark未定義和buffer中每個元素初始值爲0。它有個支持數組,其數組偏移量爲0。當capacity是負數時此方法會拋IllegalArgumentException異常。
  • ByteBuffer allocateDirect(int capacity):分配一個擁有capacity大小的直接字節buffer(後面討論)。它的position是0,它的limit就是它的capacity,mark未定義和每個元素初始值爲0。是否有一個支持數組是未指定的。當capacity是負數時此方法會拋IllegalArgumentException異常。JDK 7之前,通過此方法分配的直接buffers是在頁面邊界上對齊的。但在JDK7中,這個實現修改了以便直接buffers不在是頁面邊界對齊的了。這將減少創建大量小buffer的應用程序的內存需求。
  • ByteBuffer wrap(byte[] array):將字節數組包含到緩衝區中。這新的buffer由數組支持。就是buffer的修改會導致數組的修改,反之亦然。新的buffer的capacity和limit是數組的長度,它的position是0,mark未定義。它的數組偏移量爲0。
  • ByteBuffer wrap(byte[] array, int offset, int length) :將字節數組包含到緩衝區中。這新的buffer由數組支持。新的buffer的capacity是數組長度,position是offset,limit是offset+length,mark未定義。它的數組偏移量爲0。offset是負數或比array.length大或者length是負數或比array.length - offset大時此方法會拋IndexOutOfBoundsException異常。

這些方法展示了創建buffer的兩個方式:創建ByteBuffer對象並分配一個存儲capacity字節的內部數組或者創建ByteBuffer對象並使用指定數組去存儲這些字節。

考慮以下示例:

ByteBuffer buffer = ByteBuffer.allocate(10);
byte[] bytes = new byte[200];
ByteBuffer buffer2 = ByteBufer.wrap(bytes);

第一行創建一個字節Buffer,內部字節數組最多可存儲10個字節,第二行和第三行創建一個字節數組和一個使用該數組來存儲最多200字節的字節Buffer。

現在考慮下面的示例

buffer = ByteBuffer.wrap(bytes, 10, 50);

這個示例創建了一個字節buffer,這個buffer的position是10,limit是50,capacity是bytes.length(長度是200)。雖然出現了此buffer只可以訪問數組的子範圍,實際上是可以訪問整個數組的:數值10和50只是position和limit的起始值。
通過allocate()方法或wrap()方法創建的ByteBuffers(和其他基本數據類型buffers)是非直接字節buffers(稍後會學習直接字節buffer)。非直接字節buffers有支持數組,只要hasArray()返回true,你就可以通過array()方法訪問此支持數組(當hasArray()返回true,你需要調用arrayOffset()方法去獲取數組中第一個數據的位置。)

以下示例說明了buffer的分配和數組包含。

package com.nio.demo;

import java.nio.ByteBuffer;

public class CreateByteBufferDemo {
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        if (byteBuffer.hasArray()) {
            System.out.println("byteBuffer array: " + byteBuffer.array());
            System.out.println("byteBuffer array offset: " +
                    byteBuffer.arrayOffset());
            System.out.println("Capacity: " + byteBuffer.capacity());
            System.out.println("Limit: " + byteBuffer.limit());
            System.out.println("Position: " + byteBuffer.position());
            System.out.println("Remaining: " + byteBuffer.remaining());
        }
        System.out.println("-----------------------------------------------------");
        byte[] bytes = new byte[200];
        ByteBuffer buffer2 = ByteBuffer.wrap(bytes, 10, 50);
        if (buffer2.hasArray()) {
            System.out.println("buffer2 array: " + buffer2.array());
            System.out.println("buffer2 array offset: " +
                    buffer2.arrayOffset());
            System.out.println("Capacity: " + buffer2.capacity());
            System.out.println("Limit: " + buffer2.limit());
            System.out.println("Position: " + buffer2.position());
            System.out.println("Remaining: " + buffer2.remaining());
        }
    }
}

執行代碼,結果如下

byteBuffer array: [B@46b9979b
byteBuffer array offset: 0
Capacity: 10
Limit: 10
Position: 0
Remaining: 10
-----------------------------------------------------
buffer2 array: [B@42906563
buffer2 array offset: 0
Capacity: 200
Limit: 60
Position: 10
Remaining: 50

除了管理存儲在外部數組的元素(通過wrap()方法),buffers可以存儲於其他buffers中的數據。當創建管理其他buffers數據的buffer時,已創建的buffer被稱爲view buffer。任意一個buffer的修改都會影響另一個buffer。

View buffers通過Buffer子類的duplicate()方法來創建。view buffer的結果和原始buffer是相等的;兩個buffers共享相同數據項以及有相等的capacities。然而,每個buffer有自己的position,limit和mark。當複製的buffer是隻讀或直接時,view buffer也是隻讀或直接的。

考慮下面示例:

ByteBuffer buffer = ByteBuffer.allocate(10);
ByteBuffer viewBuffer = buffer.duplicate();

由viewBuffer定義的ByteBuffer實例與buffer共享相同的內部數組的10個元素。此時,這些buffers擁有相同的position,limit和(未定義)mark。但是,一個buffer中的這些屬性可以獨立於其他buffer中的屬性進行修改。
View buffers也可以通過調用ByteBuffer的asXBuffer()方法來創建。例如,LongBuffer asLongBuffer()返回一個view buffer,它將字節Buffer概念化爲長整數buffer。

注意 只讀的view buffer可以通過調用ByteBuffer asReadOnlyBuffer()此方法創建。任何嘗試去修改只讀view buffer內容時會拋ReadOnlyBufferException異常。然而,原始buffer內容(不是隻讀buffer提供)可以被修改,只讀的view buffer會響應這些修改。

Buffer 寫和讀

ByteBuffer和其他的原始類型buffer類定義了一些重載的put()和get()方法用於往buffer中寫入數據項或者從buffer中讀取數據項。這些方法當需要索引參數時是絕對的或不需要索引時就是相對的。

例如,ByteBuffer聲明瞭絕對索引值ByteBuffer put(int index, byte b)方法將字節b存儲在索引值的buffer中,並使用byte get(int index)方法來獲取位於索引處的字節。這個類也聲明瞭相對的方法ByteBuffer put(byte b) 將字節b存儲在當前position然後當前position加一,以及相對的byte get()方法去獲取位於buffer中的當前position的字節然後當前position加一。

當方法put()和get()傳遞的index值是負數或者大於等於buffer的limit值時會拋IndexOutOfBoundsException異常。方法put()調用時當前position大於等於limit時拋java.nio.BufferOverflowException異常以及方法get()調用時當前position大於等於limit時拋java.nio.BufferUnderflowException異常。更進一步講,當buffe是隻讀時調用put()方法會拋ReadOnlyBufferException異常。

以下清單說明了相對索引值的put()方法和絕對索引值的get()方法

package com.nio;

import java.nio.ByteBuffer;

public class BufferDemo {

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(7);
        System.out.println("Capacity = " + buffer.capacity());
        System.out.println("Limit = " + buffer.limit());
        System.out.println("Position = " + buffer.position());
        System.out.println("Remaining = " + buffer.remaining());

        buffer.put((byte) 10).put((byte) 20).put((byte) 30);

        System.out.println("Capacity = " + buffer.capacity());
        System.out.println("Limit = " + buffer.limit());
        System.out.println("Position = " + buffer.position());
        System.out.println("Remaining = " + buffer.remaining());

        for (int i = 0; i < buffer.position(); i++) {
            System.out.println(buffer.get(i));
        }
    }

}

編譯此代碼清單然後運行這個程序,你應該可以看到以下輸出:

Capacity = 7
Limit = 7
Position = 0
Remaining = 7
Capacity = 7
Limit = 7
Position = 3
Remaining = 4
10
20
30

以下圖標說明了buffer調用了3次put()方法後的狀態

隨後的三次絕對get()方法的調用不會修改這個position,這個position還是值3.

提示 爲了最大限度的提高效率,你可以使用ByteBuffer put(byte[] src), ByteBuffer put(byte[] src, int offset, int length), ByteBuffer get(byte[] dest)和ByteBuffer get(byte[] dst, int offset, int length) 方法來寫入和讀取字節數組。

翻轉Buffer

當填充完buffer後,你需要爲其準備channel來進行輸出。當你通過buffer時,channel將訪問超過當前position位置後未定義的數據。

爲了解決這個問題,你需要重置position之0,但是如何讓channel知道已經到達插入數據的終點?解決辦法就是和limit屬性一起工作,這個limit指明瞭buffer活動部分的終點。基本上,你設置limit到當前position然後重置當前position爲0。

你應該執行以下代碼完成此任務,該代碼也會清除任何定義的mark標記:

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

然而,還有一個更簡單的方式去完成同樣的任務,展示如下:

buffer.flip();

不管以上那種方式,此buffer已經準備好輸出。

假設 buffer.flip(); 是在以下清單中main()方法的最後執行,下表顯示了調用flip()後buffer的狀態。

調用buffer.remaining()會返回3,這個值指明可被輸出的字節數(10,20和30)。

以下清單提供了另一個buffer-fliping的示例,這個示例使用了字符buffer。

package com.nio;

import java.nio.CharBuffer;

public class CharBufferDemo {
    public static void main(String[] args) {
        String[] poem = {
                "Roses are red",
                "Violets are blue",
                "Sugar is sweet",
                "And so are you."
        };

        CharBuffer charBuffer = CharBuffer.allocate(50);

        for (int i = 0; i < poem.length; i++) {
            // Fill the buffer
            for (int j = 0; j < poem[i].length(); j++) {
                charBuffer.put(poem[i].charAt(j));
            }
            // Flip the buffer so that its contents can be read.
            charBuffer.flip();

            // Drain the buffer
            while (charBuffer.hasRemaining()) {
                System.out.print(charBuffer.get());
            }

            charBuffer.clear();

            System.out.println();
        }
    }
}

編譯代碼(java CharBufferDemo.java)然後運行這個程序(java CharBufferDemo),你會看到以下輸出:

Roses are red
Violets are blue
Sugar is sweet
And so are you.

注意 rewind()和flip()方法很類似,但是其忽略了limit,當然,調用flip()兩次不會返回到原始狀態。相反,此buffer有一個0的大小,調用put()方法結果拋BufferOverflowException異常,調用get()方法結果拋BufferUnderflowException異常或者(調用get(int)方法這種情形下)拋IndexOutOfBoundsException異常。

Buffers 標記

你可以通過mark()方法來標記一個buffer和通過reset()方法來返回已標記的位置。例如,假如你已經執行了一下代碼:

ByteBuffer buffer = ByteBuffer.allocate(7);
buffer.put((byte)10).put((byte)20).put((byte)30).put((byte)40);
buffer.limit(4);

此buffer的當前position是4。
繼續,假如你又執行了buffer.position(1).mark().position(3);下表指明瞭當前buffer的在此點的狀態。

如果你把此buffer發送給一個channel,字節40將會被髮送(當前position是3因爲position(3))和position會被提高到4.如果你順序執行buffer.reset();然後發送此buffer到一個channel中,此時position將被設置爲mark(1)和字節20,30和40(從當前position到limit前一個位置的所有字節)將會被髮送到這個channel中(按此順序)。

以下清單說明了mark/reset情形。

package com.nio;

import java.nio.ByteBuffer;

public class MarkBufferDemo {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(7);
        buffer.put((byte) 10).put((byte) 20).put((byte) 30).put((byte) 40);
        buffer.limit(4);
        buffer.position(1).mark().position(3);
        System.out.println(buffer.get());
        System.out.println();
        buffer.reset();
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());
        }
    }
}

編譯代碼(java MarkBufferDemo.java)然後運行這個程序(java MarkBufferDemo),你會看到以下輸出:

40

20
30
40

警告 不要混淆reset()和clear()兩個方法,clear()方法將buffer標記爲空,而reset()方法將buffer的當前position標記爲先前設置的標記位,或者在沒有先前標記時拋出InvalidMarkException異常。

Buffer的子類操作

ByteBuffer和其他原始類型的buffer類聲明瞭compact()方法,通過此方法將buffer中從當前position到limit的所有字節複製到此buffer的開始位置來壓縮buffer。在索引p = position()位置的字節被複制到索引爲0的位置,在索引p + 1位置的字節被複制到索引1位置,以此類推直到索引limit() - 1的位置被複制到索引 n = limit() - 1 - p的位置上。此buffer當前position設置爲n + 1,它的limit設置爲capacity。這些mark,如果已定義,則廢棄。

在從buffer寫入數據後調用compact()方法來處理不是所有buffer內容被寫入的情況。考慮以下例子,通過 buffer buf從一個輸入channel複製內容到一個輸出channel中:

buf.clear();// 準備buffer
while(in.read(buf) != -1) {
    buf.flip();// 準備buffer寫出
    out.write(buf);// 寫出buffer
    buf.compact();// 部分寫出是這樣做
}

compact()方法的調用會將未寫入的buffer數據移動到buffer的初始位置以便下次調用read()方法時讀取的數據追加到buffer的數據,而不是在未指定compact()是覆蓋該數據。

偶爾你可能需要比較buffer是否相等或者排序。除了ByteBuffer’s MappedByteBuffer子類外所有Buffer子類都重寫了equals()和compareTo()方法來執行這些比較操作–MappedByteBuffer是從ByteBuffer父類中繼承了這些方法。下面的示例向你展示了怎樣比較字節buffer byteBuf1和ByteBuf2 的相等和排序:

System.out.println(byteBuf1.equals(byteBuf2));
System.out.println(byteBuf1.compareTo(byteBuf2));

ByteBuffer中的equals()合同規定,當且僅當他們具有相同的元素類型時,兩個字節buffer相等;他們具有相同數量的剩餘元素;並且獨立於他們的起始位置考慮的剩餘元素的兩個序列是單獨相等的。該合同對於其他子類也是相同的。

ByteBuffer中的compareTo()方法表明,通過將字節序列中的剩餘元素的序列進行比較來比較兩個字節buffer的順序,而不考慮對應buffer內每個序列的起始位置。比較一下字節元素,就像調用Byte.compare(byte, byte)方法一樣。類似的描述同樣適用於其他buffer的子類中。

Byte 排序

除了Boolean類型(由一位或一個字節表示)其他非字節基本數據類型是由多字節組成的:一個字符或者一個短整型由2個字節組成,32位整型或浮點型由4個字節組成,長整型或雙精度浮點型是由8個字節組成。這些多字節類型中的一個的每個值被存儲在連續的存儲器位置的序列中。然而不同操作系統之間這些字節的順序是不同的。

例如,考慮32位長整型0x10203040。這個值得四個字節可能存儲在內存上(從低位到高位)是10,20,30,40;這樣安排被稱爲大端序(最重要的字節,大端,存儲在最低地址)。或者,這些字節可以存儲爲40,30,20,10;這樣的安排被稱爲小端序(最不重要的字節,小端,被存儲在最低的地址)。

當從多字節buffe中寫入/讀取時,java提供了java.nio.ByteOrder類來幫助你處理字節排序問題。ByteOrder聲明瞭ByteOrder nativeOrder()方法,它作爲ByteOrder實例來返回操作系統的字節順序。因爲這個實例是ByteOrder的BIG_ENDIAN和LITTLE_ENDIAN常量之一,也因爲沒有其他的ByteOrder實例被創建,你可以通過==或!=操作符來將nativeOrder()的返回值與其中一個常量進行比較。

每一個多字節buffer類(例如FloatBuffer)聲明瞭ByteOrder order()方法來返回這個buffer字節的排序。這個方法返回ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN。

從order()返回的ByteOrder值可以根據Buffer的創建方式而取得不同的值。如果一個多字節buffer(例如一個浮點型buffer)通過分配或包裝一個已存在的數組來創建,這個buffer的字節順序就是底層操作系統的本地排序。然而,如果一個多字節buffer作爲一個字節buffer視圖被創建,當這個視圖創建時,此buffer視圖的字節排序就是這個字節buffer的排序。之後這個視圖buffer的字節排序不能被修改。

當涉及到字節排序時ByteBuffer與其他的多字節buffer是不同的。它的默認字節排序總是大端,即使底層操作系統的字節排序是小端。ByteBuffer默認是大端排序時因爲Java的默認字節排序也是大端,這就使得類文件和序列化對象存儲在JVM中一致的存儲數據。

因爲這個大端序列的默認值會影響小端操作系統的性能,所以ByteBuffer也聲明瞭一個ByteBuffer命令(ByteOrder bo)來改變字節Buffer的字節順序。

雖然改變字節Buffer(僅訪問單字節數據項)的字節順序似乎不尋常,但此方法非常有用,因爲ByteBuffer還聲明瞭寫入和讀取多字節值的方便方法(例如,ByteBuffer putInt(int value) 和int getInt())。 這些方便方法根據字節Buffer的當前字節順序寫入這些值。 此外,您可以隨後將ByteBuffer的LongBuffer稱爲LongBuffer()或另一個asxBuffer()方法返回一個視圖Buffer,其順序將反映該字節Buffer改變了字節順序。

直接字節Buffers

與多字節Buffers不同,字節Buffers可以作爲基於channel I/O的數據源/目的地。這不能作爲一個驚喜因爲操作系統在內存區域中執行I/O操作,這些內存區域是連續的8位字節的序列上(不是浮點值,也不是32位整型等等)。

操作系統可以直接訪問進程的地址空間。例如,操作系統可以直接訪問JVM進程的地址空間去執行基於字節數組的數據轉移操作。然而,JVM可能不會連續存儲字節數組或者垃圾收集器可能將字節數組移動到其他位置。由於這些限制就創建了直接字節buffer。

直接字節buffer是一個與channels和本機代碼交互去執行I/O操作的字節buffer。直接字節buffer嘗試在內存區域中存儲在channel中用於通過本地代碼執行直接(原始)訪問的內存區域中,該本地代碼告訴操作系統直接排除或填充內存區域。

直接字節Buffers在JVM上執行I/O操作是最有效的。雖然你也可以傳輸非直接字節buffer到channels,可能出現性能問題因爲非直接buffer不總是能夠作爲本地I/O操作的目標。

當傳送一個非直接字節buffer給channel時,它會創建一個臨時的直接字節buffer,將非直接字節buffer的內容拷貝到直接字節buffer中,會在臨時直接字節buffer上執行I/O操作,然後拷貝臨時直接字節buffer的內容到內直接字節buffer中。然後臨時直接字節buffer將被垃圾收集器回收。

雖然對於I/O操作最佳,直接字節buffer的創建是很昂貴的因爲操作系統需要分配JVM堆以外的內存,設置/拆除此內存可能要比位於堆內的buffer的時間更長。

在你的代碼工作之後,如果你想優化性能,你可以調用ByteBuffer allocateDirect()方法輕鬆獲得一個直接字節buffer,這個我稍後討論。

總結

buffer是一個NIO對象,它存儲要發送給I/O服務或從I/O服務中接受的固定數量的數據。它位於應用程序和將緩衝數據寫入服務或從服務讀取數據並將其存入buffer的channel之間。

Buffers具有capacity,limit,position以及mark屬性。這四個屬性關係如下:0 <= mark <= position <= limit <= capacity。

Buffers由派生自抽象類Buffer的抽象類實現。這些類包含ByteBuffer, CharBuffer, DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer和ShortBuffer。進一步將,ByteBuffer是抽象類MappedByteBuffer的子類。

在這章,你學習了怎麼創建buffers(包含視圖buffer),讀和寫buffer內容,翻轉buffer,標記buffer和在buffer上執行額外操作例如壓縮。也學習了關於字節排序和直接字節buffers。

說明

終於翻譯完這一章了,中間有翻譯不周的請各位看官諒解。下一章就開始NIO的另一個組件Channel了,一起期待一下吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章