1. 程式人生 > >通俗編程——白話NIO之Buffer

通俗編程——白話NIO之Buffer

popu 演示 過程 操作 等於 char alloc this new

Buffer簡單介紹

Buffer意為緩沖區。其本質上就是是一塊可寫入數據,然後能夠從中讀取數據的內存區域。通過該種方式有助於降低系統開銷和提高外設效率。對於緩沖區我們早有所了解,比方在C中標準I/O中的read,write直接調用系統的輸入輸出。而scanf和printf則借助緩沖區在適當的時候調用read。write操作。在NIO中,為了方便對緩沖區的操作。jAVA設計者將緩沖區封裝為Buffer(實際上就是封裝了基本數據元素的數組),並提供對應的方法對其操作。

在開始之前,首先須要明確下面幾個概念:

  • 讀模式和寫模式
  • Buffer的capacity
  • Buffer的position
  • Buffer的limit

讀模式和寫模式

對於一塊內存區而言,將數據寫入該區域的過程稱之為寫模式。反之,稱之為讀模式。

capacity

該內存的大小即數組的的容量稱為Buffer的capacity。一旦Buffer滿了,你須要將其清空才幹繼續寫入數據。

position

Buffer當前遊標的位置稱為position。在寫模式下position表示當前的位置。初始的position值為0.當一個byte、long等數據寫到Buffer後,position會向前移動到下一個可插入數據的Buffer單元。position最大可為capacity – 1。當將Buffer從寫模式切換到讀模式,position會先被重置為0,然後當從Buffer的position處讀取數據時。position向前移動到下一個可讀的位置。

limit

在寫模式下,Buffer的limit表示你最多能往Buffer裏寫多少數據。寫模式下,limit等於Buffer的capacity。當切換Buffer到讀模式時。limit表示你最多能讀到多少數據。因此,當切換Buffer到讀模式時。limit會被設置成寫模式下的position值。

換句話說,你能讀到之前寫入的全部數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position)

我們用一張圖來大體的概括:
技術分享


Buffer使用

Buffer的使用過程大體遵循下面步驟:
分配緩存大小——>寫數據到Buffer——>調用其filp()——>從Buffer讀取數據——>調用clear()或者compact()。


當向buffer寫入數據時。buffer會記錄下寫了多少數據。

一旦要讀取數據,須要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下。能夠讀取之前寫入到buffer的全部數據。一旦讀完了全部的數據。就須要清空緩沖區,讓它能夠再次被寫入。

1. 創建Buffer

通過allocate()或者通過wrap()方法

  ByteBuffer byteBuffer=ByteBuffer.allocate(48)

  Byte[] bytes=new Byte[1024];
  //通過該種方式將生成一個limit=capacity=bytes.length的新緩存區,假設bytes裏含有數據,則會用該數據填充緩沖區。要註意的是通過該種方式創建的ByteBuffer其position初始值是0.
  ByteBuffer byteBuffer=ByteBuffer.wrap(bytes);

2. 向Buffer中寫數據

通過Channel的read方法。將數據寫到Buffer
通過Buffer自身的put()方法

int bytesRead=fileChannel.read(byteBuffer);
byteBuffer.put(2);

3. 通過filp()進行模式轉換

flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會首先將limit的值設為當前position的值。然後將position設為0。相當運行了下面語句:
limit=position;
position=0;

換句話說,position如今用於標記讀的位置,limit表示之前寫進了多少個byte、char等 —— 如今能讀取多少個byte、char等。

4. 讀取Buffer的數據

從Buffer讀取數據到Channel
使用Buffer的get()方法讀取Buffer的數據

5. 重讀buffer中的數據

Buffer.rewind()將position設回0,所以你能夠重讀Buffer中的全部數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。

6. 清空Buffer

一旦讀完Buffer中的數據,須要讓Buffer準備好再次被寫入。能夠通過clear()或compact()方法來完畢。
假設調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。換句話說,Buffer相當於 被清空了(實際上Buffer中的內容並未真正被清空,此時假設調用rewind()或者設置position=0仍然可讀取舊的數據)。

該方法實際上僅僅是重設了position和limit的值。進而告訴我們能夠從哪裏開始往Buffer裏寫數據。
假設Buffer中有一些未讀的數據,調用clear()方法,數據將“被遺忘”,意味著不再有不論什麽標記會告訴你哪些數據被讀過。哪些還沒有。

假設Buffer中仍有未讀的數據。且興許還須要這些數據,可是此時想要先先寫些數據。那麽使用compact()方法。
compact()方法將全部未讀的數據復制到Buffer起始處。

然後將position設到最後一個未讀元素正後面。limit屬性依舊像clear()方法一樣,設置成capacity。如今Buffer準備好寫數據了,可是不會覆蓋未讀的數據。

7. 通過mark()標記position位置。在進行其它操作後可通過reset() 方法恢復到標記的position

buffer.mark()
...
buffer.reset()

Buffer完整演示樣例

public class testBuffer {
    public static void main(String[] args) {
        /**
         * 分配空間 隱含地在內存中分配了一個byte型數組來存儲10個byte
         */
        ByteBuffer buffer = ByteBuffer.allocate(10);
        /**
         * 填充元素 buffer.hasRemaining()用於推斷緩沖區是否達到上界limit。 該填充過程等效於:int remainCount = buffer.remaining();for (int j = 0; j < remainCount;
         * j++){buffer.put((byte) j++);}
         */
        int i = 0;
        while (buffer.hasRemaining()) {
            buffer.put((byte) i++);
        }
        /**
         * 翻轉緩沖區 將緩沖區進行翻轉操作,即在緩沖區寫入完畢時,將緩沖區翻轉成一個準備讀出元素的狀態。 flip操作等效於buffer.limit(buffer.position()).position(0);同一時候將mark設為-1。 源代碼例如以下:public final Buffer
         * flip(){ limit = position;position = 0;mark = -1;return this;}
         */
        buffer.flip();
        /**
         * 讀取緩沖區
         */
        int remainCount = buffer.remaining();
        for (int j = 0; j < remainCount; j++) {
            System.out.print(buffer.get() + " ");
        }
        System.out.println();

        /**
         * 字節順序
         */
        System.out.println("ByteOrder的字節順序為:" + ByteOrder.nativeOrder());
        System.out.println("ByteBuffer的字節順序為:" + buffer.order());
        CharBuffer charBuffer = CharBuffer.allocate(10);
        System.out.println("CharBuffer的字節順序為:" + charBuffer.order());
        // 改動ButyBuffer的字節順序
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        System.out.println("ByteBuffer的字節順序為:" + buffer.order());

        /**
         * 僅僅有ByteBuffer能夠創建直接緩沖區,用wrap函數創建的緩沖區都是非直接的緩沖區
         */
        ByteBuffer redirectByteBuffer = ByteBuffer.allocateDirect(10);
        System.out.println("推斷緩沖區是否為直接緩沖區:" + redirectByteBuffer.isDirect());

        /**
         * 先創建一個大端字節順序的ByteBuffer。然後再創建一個字符視圖緩沖區
         */
        ByteBuffer bigByteBuffer = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
        CharBuffer viewCharBuffer = bigByteBuffer.asCharBuffer();

        viewCharBuffer.put("你好啊");
        /**
         * 在字符視圖的基礎上創建僅僅讀字符視圖,僅僅能讀而不能寫,否則拋出ReadOnlyBufferException
         */
        CharBuffer onlyReadCharBuffer = viewCharBuffer.asReadOnlyBuffer();

        viewCharBuffer.flip();
        System.out.println("asCharBuffer()--->position=" + viewCharBuffer.position() + ",limit=" + viewCharBuffer.limit());
        while (viewCharBuffer.hasRemaining()) {
            System.out.println((char) viewCharBuffer.get());
        }

        /**
         * 創建一個與原始緩沖區類似的新緩沖區,兩個緩沖區共享數據元素,擁有相同的容量,但每一個緩沖區擁有各自的位置、上界、標記屬性。 對一個緩沖區的數據元素所做的改變會反映在還有一個緩沖區上。新的緩沖區會繼承原始緩沖區的這些屬性。

*/ CharBuffer copyCharBuffer = viewCharBuffer.duplicate(); System.out.println("duplicate()--->position=" + copyCharBuffer.position() + ",limit=" + copyCharBuffer.limit()); copyCharBuffer.position(2); while (copyCharBuffer.hasRemaining()) { System.out.println((char) copyCharBuffer.get()); } /** * 創建原始緩沖區子集的新緩沖區,新緩沖區的內容將從該緩沖區的當前位置開始。對這個緩沖區的內容做出的改變將在反映在新的緩沖區上,可見,反之亦然;這兩個緩沖區的位置、限制和標記值將是獨立的。 */ viewCharBuffer.position(1); CharBuffer cutCharBuffer = viewCharBuffer.slice(); System.out.println("slice()--->position=" + cutCharBuffer.position() + ",limit=" + cutCharBuffer.limit()+",capacity="+cutCharBuffer.capacity()); while (cutCharBuffer.hasRemaining()) { System.out.println((char) cutCharBuffer.get()); } } }


通俗編程——白話NIO之Buffer