Java NIO學習系列一:Buffer

  前面三篇文章中分別總結了標準Java IO系統中的File、RandomAccessFile、I/O流系統,對於I/O系統從其繼承體系入手,力求對類數量繁多的的I/O系統有一個清晰的認識,而後結合一些I/O的常規用法來加深對標準I/O系統的掌握,感興趣的同窗能夠看一下:html

  <<Java I/O系統學習系列一:File和RandomAccessFile>>數組

  <<Java I/O系統學習系列二:輸入和輸出>>網絡

  <<Java I/O系統學習系列三:I/O流的典型使用方式>>app

  從本文開始我會開始總結NIO部分,Java NIO(注意,這裏的NIO其實叫New IO)是用來替換標準Java IO以及Java 網絡API的,其提供了一系列不一樣與標準IO API的方式來處理IO,從JDK1.4開始引入,其目的在於提升速度。dom

  之因此可以提升速度是由於其所使用的結構更接近於操做系統執行I/O的方式:通道和緩衝器。咱們能夠把它想象成一個煤礦,通道是一個包含煤層(數據)的礦藏,而緩衝器則是派送到礦藏的卡車。卡車滿載煤炭而歸,咱們再從卡車上得到煤炭。也就是說,咱們並無直接和通道交互,而是和緩衝器交互,並把緩衝器派送到通道。通道要麼從緩衝器得到數據,要麼向緩衝器發送數據。學習

   在標準IO的API中,使用字節流和字符流。而在Java NIO中是使用Channel(通道)和Buffer(緩衝區),數據從channel中讀取到buffer中,或從buffer寫入到channel中。Java NIO類庫中的核心組件爲:spa

  • Buffer
  • Channel
  • Selector

  本文中咱們會着重總結Buffer相關的知識點(後面的文章中會繼續介紹Channel即Selector),本文主要會圍繞以下幾個方面展開:操作系統

  Buffer簡介code

  Buffer的內部結構  xml

  Buffer的主要API

  ByteBuffer

  Buffer類型

  總結

 

1. Buffer簡介

  Java NIO中的Buffer通常和Channel配對使用。能夠從Channel中讀取數據到Buffer,或者寫數據到Channel中。一個Buffer其實就是表明一個內存塊,你能夠往裏面寫數據或者從中讀取數據。這個內存塊被包裝成一個Buffer對象,而且提供了一系列方法使得操做內存塊更便捷。

  經過Buffer來讀寫數據一般包括以下4步:

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

  當往Buffer中寫數據時,Buffer可以記錄寫了多少數據。當要從Buffer中讀取數據時,就須要經過調用flip()方法將Buffer從寫模式切換到讀模式。一旦讀完全部數據,須要清空Buffer,讓它再次處於寫狀態。能夠經過調用clear()或compact()方法來完成這一步:

  • clear()方法會清空整個Buffer;
  • compact()方法僅僅清空你已經從Buffer中讀取的數據,未讀數據會被移動到Buffer起始位置,能夠緊接着未讀的數據寫入新的數據;

  以下是一個簡單的使用例子,經過FileChannel和ByteBuffer讀取pom.xml文件,並逐字節輸出:

public class BufferDemo {

    public static void main(String[] args) {
        try {
            RandomAccessFile raf = new RandomAccessFile("pom.xml","r");
            FileChannel channel = raf.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(48);
            int byteReaded = channel.read(buffer);
            while(byteReaded != -1) {
                buffer.flip();
                while(buffer.hasRemaining()) {
                    System.out.print((char)buffer.get());
                }
                buffer.clear();
                byteReaded = channel.read(buffer);
            }
            raf.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }    
}

 

2. Buffer的內部結構

  上面說到Buffer封裝了一塊內存塊,並提供了一系列的方法使得能夠方便地操縱內存中的數據。至於如何操縱?Buffer提供了4個索引。要理解Buffer的工做原理,就須要從這些索引提及:

  • capacity(容量);
  • position(位置);
  • limit(界限);
  • mark(標記);

   其中position和limit的含義取決於Buffer是處於什麼模式(讀或者寫模式),capacity的含義則和模式無關,而mark則只是一個標記,能夠經過mark()方法進行設置。下圖描述了讀寫模式下三種屬性分別表明的含義,詳細解釋見下文:

2.1 Capacity

  Buffer表明一個內存塊,因此其是有肯定大小的,也叫「容量」。能夠往buffer中寫入各類數據如byte、long、chars等,當Buffer被寫滿了則須要將其清空(能夠經過讀取數據或者清空數據)以後才能繼續寫入數據。

2.2 Position

  當往Buffer中寫數據時,寫入的地方就是所謂的position,其初始值爲0,最大值爲capacity-1。當往Buffer中寫入一個byte或者long的數據時,position會前移以指向下一個即將被插入的位置。

  當從Buffer中讀取數據時,讀取數據的地方就是所謂的position。當執行flip將Buffer從寫模式切換到讀模式時,position會被重置爲0。隨着不斷從Buffer讀取數據,position也會不斷後移指向下一個將被讀取的數據。

2.3 Limit

  在寫模式下,Buffer的limit是指可以往Buffer中寫入多少數據,其值等於Buffer的capacity。

  在讀模式下,Buffer的limit是指可以從Buffer讀取多少數據出來。所以當從寫模式切換到讀模式下時,limit就被設置爲寫模式下的position的值(這很好理解,寫了多少才能讀到多少)。

 2.4 Mark

  mark其實就是一個標記,能夠經過mark()方法設置,設置值爲當前的position。

 

  下面是用於設置和復位索引以及查詢它們值的方法:


 

  capacity()      返回緩衝區容量
  clear()      清空緩衝區,將position設置爲0,limit設置爲容量。咱們能夠調用此方法覆寫緩衝區
  flip()       將limit設置爲position,position設置爲0。此方法用於準備從緩衝區讀取已經寫入的數據
  limit()        返回limit值
  limit(int lim)    設置limit值
  mark()       將mark設置爲position
  position()     返回position值
  position(int pos)  設置position值
  remaining()    返回(limit - position)
  hasRemaining()  如有介於position和limit之間的元素,則返回true


 

3. Buffer的主要API

  除了如上和索引相關的方法以外,Buffer還提供了一些其餘的方法用於寫入、讀取等操做。

3.1 給Buffer分配空間

  要得到一個Buffer對象就能夠經過Buffer類的allocate()方法來實現,以下分別是分配一個48字節的ByteBuffer和1024字符的CharBuffer:

ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);

3.2 往Buffer中寫數據

  有兩種方式往Buffer中寫入數據:

  • 從Channel中往Buffer寫數據;
  • 經過Buffer的put()方法寫入數據;
int bytesRead = inChannel.read(buf); // read into buffer
buf.put(127);

  put()方法有多個重載版本,好比從指定位置寫入數據,或寫入字節數組等。

3.3 flip()

  flip()方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設爲0,limit設爲position以前的值。

3.4 從Buffer讀數據

  也有兩種方法從Buffer讀取數據:

  • 從Buffer中讀數據到Channel中;
  • 調用Buffer的get()方法讀取數據;
int bytesWritten = inChannel.write(buf); // read from buffer into channel
byte aByte = buf.get();

3.5 rewind()

  rewind()方法將position設置爲0,能夠從頭開始讀數據。

3.6 clear()和compact()

  當從Buffer讀取數據結束以後要將其切換回寫模式,能夠調用clear()、compact()這兩個方法,二者之間的區別以下:

  調用clear(),會將position設爲0,limit設爲capacity,也就是說Buffer被清空了,可是裏面的數據仍然存在,只是這時沒有標記能夠告訴你哪些數據是已讀,哪些是未讀。

  若是讀取到一半須要寫入數據,可是未讀的數據稍後還須要讀取,這時可使用compact(),其會將全部未讀取的數據複製到Buffer的前面,將position設置到這些數據後面,limit設置爲capacity,因此此時是從未讀的數據後面開始寫入新的數據。

3.7 mark()和reset()

  調用mark()方法能夠標誌一個指定的位置(即設置mark值),以後調用reset()方法時position又會回到以前標記的位置。

 

4. ByteBuffer

   ByteBuffer是一個比較基礎的緩衝器,繼承自Buffer,是能夠存儲未加工字節的緩衝器,而且也是惟一直接與通道交互的緩衝器。能夠經過ByteBuffer的allocate()方法來分配一個固定大小的ByteBuffer,而且其還有一個方法選擇集,用於以原始的字節形式或基本類型輸出和讀取數據。可是,沒辦法輸出或讀取對象,即便是字符串對象也不行。這種處理雖然很低級,但卻正好,由於這是大多數操做系統中更有效的映射方式。

  ByteBuffer也分爲直接和非直接緩衝器,經過allocate()建立的就是非直接緩衝器,而經過allocateDirect()方法就能夠建立出一個緩衝器直接緩衝器,這是一個與操做系統有更高耦合性的緩衝器,也就意味着它可以帶來更高的速度,可是分配的開支也會更大。

  儘管ByteBuffer只能保存字節類型的數據,可是它具備能夠從其所容納的字節中產生出各類不一樣基本類型值的方法。下面的例子展現怎樣使用這些方法來插入和抽取各類數值:

public class GetData {    
    private static final int BSIZE = 1024;
    public static void main(String[] args){
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        int i = 0;
        while(i++ < bb.limit())
            if(bb.get() != 0)
                System.out.println("nonzero");
        System.out.println("i = " + i);
        bb.rewind();
        // store and read a char array:
        bb.asCharBuffer().put("Howdy!");
        char c;
        while((c = bb.getChar()) != 0)
            System.out.print(c + " ");
        System.out.println();
        bb.rewind();
        // store and read a short:
        bb.asShortBuffer().put((short)471142);
        System.out.println(bb.getShort());
        bb.rewind();
        // sotre and read an int:
        bb.asIntBuffer().put(99471142);
        System.out.println(bb.getInt());
        bb.rewind();
        // store and read a long:
        bb.asLongBuffer().put(99471142);
        System.out.println(bb.getLong());
        bb.rewind();
        // store and read a float:
        bb.asFloatBuffer().put(99471142);
        System.out.println(bb.getFloat());
        bb.rewind();
        // store and read a double:
        bb.asDoubleBuffer().put(99471142);
        System.out.println(bb.getDouble());
        bb.rewind();
    }
}

 

5. Buffer類型

  Java NIO中包含了以下幾種Buffer:

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

  這些Buffer類型表明着不一樣的數據類型,使得能夠經過Buffer直接操做如char、short等類型的數據而不是字節數據。其中MappedByteBuffer略有不一樣,後面會專門總結。

  經過ByteBuffer咱們只能往Buffer直接寫入或者讀取字節數組,可是經過對應類型的Buffer好比CharBuffer、DoubleBuffer等咱們能夠直接往Buffer寫入char、double等類型的數據。或者利用ByteBuffer的asCharBuffer()、asShorBuffer()等方法獲取其視圖,而後再使用其put()方法便可直接寫入基本數據類型,就像上面的例子。

  這就是視圖緩衝器(view buffer)可讓咱們經過某個特定的基本數據類型的視窗查看其底層的ByteBuffer。ByteBuffer依然是實際存儲數據的地方,「支持」着前面的視圖,所以對視圖的任何修改都會映射成爲對ByteBuffer中數據的修改。這使得咱們能夠很方便地向ByteBuffer插入數據。視圖還容許咱們從ByteBuffer一次一個地(與ByteBuffer所支持的方式相同)或者成批地(經過放入數組中)讀取基本類型值。在下面的例子中,經過IntBuffer操縱ByteBuffer中的int型數據:

public class IntBufferDemo {    
    private static final int BSIZE = 1024;
    public static void main(String[] args){
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        IntBuffer ib = bb.asIntBuffer();
        // store an array of int:
        ib.put(new int[]{11,42,47,99,143,811,1016});
        // absolute location read and write:
        System.out.println(ib.get(3));
        ib.put(3,1811);
        // setting a new limit before rewinding the buffer.
        ib.flip();
        while(ib.hasRemaining()){
            int i = ib.get();
            System.out.println(i);
        }
    }
}

  上例中先用重載後的put()方法存儲一個整數數組。接着get()和put()方法調用直接訪問底層ByteBuffer中的某個整數位置。這些經過直接與ByteBuffer對話訪問絕對位置的方式也一樣適用於基本類型。

 

6. 總結

  本文簡單總結了Java NIO(Java New IO),其目的在於提升速度。Java NIO類庫中主要包括Buffer、Channel、Selector,本文主要總結了Buffer相關的知識點:

  • Buffer叫緩衝器,她是和Channel(通道)交互的,能夠從channel中讀數據到buffer中,或者從buffer往channel中寫數據;
  • Buffer內部封裝了一塊內存,提供了一系列API使得能夠方便地操做內存中的數據。其內部是經過capacity、position、limit、mark等變量來跟蹤標記封裝的數據的;
  • ByteBuffer是最基本的Buffer,是惟一能夠直接與通道交互的緩衝器,其能夠直接操縱字節數據或字節數組;
  • 除了ByteBuffer以外,Buffer還有許多別的類型如:MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer;
  • 雖然只有ByteBuffer可以直接和通道交互,可是能夠從ByteBuffer獲取多種不一樣的視圖緩衝器,進而同時具有了直接操做基本數據類型和與通道交互的能力;

  基礎知識的總結也許是比較枯燥的,可是若是你已經看到這裏說明你頗有耐心,若是以爲對你有幫助的話,不妨點個贊關注一下吧^_^

相關文章
相關標籤/搜索