• <input id="qucwm"><u id="qucwm"></u></input>
  • <menu id="qucwm"></menu>
  • <input id="qucwm"><tt id="qucwm"></tt></input>
  • <input id="qucwm"><acronym id="qucwm"></acronym></input>
  • Java NIO系列教程(三) Buffer

    原文鏈接? ?? 作者:Jakob Jenkov ? ??譯者:airu ? ??校對:丁一

    Java NIO中的Buffer用于和NIO通道進行交互。如你所知,數據是從通道讀入緩沖區,從緩沖區寫入到通道中的。

    緩沖區本質上是一塊可以寫入數據,然后可以從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,并提供了一組方法,用來方便的訪問該塊內存。

    下面是NIO Buffer相關的話題列表:

    1. Buffer的基本用法
    2. Buffer的capacity,position和limit
    3. Buffer的類型
    4. Buffer的分配
    5. 向Buffer中寫數據
    6. flip()方法
    7. 從Buffer中讀取數據
    8. clear()與compact()方法
    9. mark()與reset()方法
    10. equals()與compareTo()方法

    Buffer的基本用法

    使用Buffer讀寫數據一般遵循以下四個步驟:

    1. 寫入數據到Buffer
    2. 調用flip()方法
    3. 從Buffer中讀取數據
    4. 調用clear()方法或者compact()方法

    當向buffer寫入數據時,buffer會記錄下寫了多少數據。一旦要讀取數據,需要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到buffer的所有數據。

    一旦讀完了所有的數據,就需要清空緩沖區,讓它可以再次被寫入。有兩種方式能清空緩沖區:調用clear()或compact()方法。clear()方法會清空整個緩沖區。compact()方法只會清除已經讀過的數據。任何未讀的數據都被移到緩沖區的起始處,新寫入的數據將放到緩沖區未讀數據的后面。

    下面是一個使用Buffer的例子:

    RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
    FileChannel inChannel = aFile.getChannel();
    
    //create buffer with capacity of 48 bytes
    ByteBuffer buf = ByteBuffer.allocate(48);
    
    int bytesRead = inChannel.read(buf); //read into buffer.
    while (bytesRead != -1) {
    
      buf.flip();  //make buffer ready for read
    
      while(buf.hasRemaining()){
          System.out.print((char) buf.get()); // read 1 byte at a time
      }
    
      buf.clear(); //make buffer ready for writing
      bytesRead = inChannel.read(buf);
    }
    aFile.close();
    

    Buffer的capacity,position和limit

    緩沖區本質上是一塊可以寫入數據,然后可以從中讀取數據的內存。這塊內存被包裝成NIO Buffer對象,并提供了一組方法,用來方便的訪問該塊內存。

    為了理解Buffer的工作原理,需要熟悉它的三個屬性:

    • capacity
    • position
    • limit

    position和limit的含義取決于Buffer處在讀模式還是寫模式。不管Buffer處在什么模式,capacity的含義總是一樣的。

    這里有一個關于capacity,position和limit在讀寫模式中的說明,詳細的解釋在插圖后面。

    capacity

    作為一個內存塊,Buffer有一個固定的大小值,也叫“capacity”.你只能往里寫capacity個byte、long,char等類型。一旦Buffer滿了,需要將其清空(通過讀數據或者清除數據)才能繼續寫數據往里寫數據。

    position

    當你寫數據到Buffer中時,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的類型

    Java NIO 有以下Buffer類型

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

    p<>
    如你所見,這些Buffer類型代表了不同的數據類型。換句話說,就是可以通過char,short,int,long,float 或 double類型來操作緩沖區中的字節。

    MappedByteBuffer 有些特別,在涉及它的專門章節中再講。

    Buffer的分配

    要想獲得一個Buffer對象首先要進行分配。 每一個Buffer類都有一個allocate方法。下面是一個分配48字節capacity的ByteBuffer的例子。

    ByteBuffer buf = ByteBuffer.allocate(48);
    

    這是分配一個可存儲1024個字符的CharBuffer:

    CharBuffer buf = CharBuffer.allocate(1024);
    

    向Buffer中寫數據

    寫數據到Buffer有兩種方式:

    • 從Channel寫到Buffer。
    • 通過Buffer的put()方法寫到Buffer里。

    從Channel寫到Buffer的例子

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

    通過put方法寫Buffer的例子:

    buf.put(127);
    

    put方法有很多版本,允許你以不同的方式把數據寫入到Buffer中。例如, 寫到一個指定的位置,或者把一個字節數組寫入到Buffer。 更多Buffer實現的細節參考JavaDoc。

    flip()方法

    flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,并將limit設置成之前position的值。

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

    從Buffer中讀取數據

    從Buffer中讀取數據有兩種方式:

    1. 從Buffer讀取數據到Channel。
    2. 使用get()方法從Buffer中讀取數據。

    從Buffer讀取數據到Channel的例子:

    //read from buffer into channel.
    int bytesWritten = inChannel.write(buf);
    

    使用get()方法從Buffer中讀取數據的例子

    byte aByte = buf.get();
    

    get方法有很多版本,允許你以不同的方式從Buffer中讀取數據。例如,從指定position讀取,或者從Buffer中讀取數據到字節數組。更多Buffer實現的細節參考JavaDoc。

    rewind()方法

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

    clear()與compact()方法

    一旦讀完Buffer中的數據,需要讓Buffer準備好再次被寫入??梢酝ㄟ^clear()或compact()方法來完成。

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

    如果Buffer中有一些未讀的數據,調用clear()方法,數據將“被遺忘”,意味著不再有任何標記會告訴你哪些數據被讀過,哪些還沒有。

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

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

    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()與compareTo()方法

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

    equals()

    當滿足下列條件時,表示兩個Buffer相等:

    1. 有相同的類型(byte、char、int等)。
    2. Buffer中剩余的byte、char等的個數相等。
    3. Buffer中所有剩余的byte、char等都相同。

    如你所見,equals只是比較Buffer的一部分,不是每一個在它里面的元素都比較。實際上,它只比較Buffer中的剩余元素。

    compareTo()方法

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

    1. 第一個不相等的元素小于另一個Buffer中對應的元素 。
    2. 所有元素都相等,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數比另一個少)。

    (譯注:剩余元素是從 position到limit之間的元素)

    原創文章,轉載請注明: 轉載自并發編程網 – www.okfdzs1913.com本文鏈接地址: Java NIO系列教程(三) Buffer


    FavoriteLoading添加本文到我的收藏
    • Trackback 關閉
    • 評論 (33)
      • NinetyH
      • 2013/06/26 11:47上午

      Hi, compact() 方法被調用之后, limit 不應該等于capacity ! 我覺得應該是: limit = capacity – (position + 1)。因為未讀的數據還在占用buffer 容量!

      • NinetyH
      • 2013/06/26 2:40下午

      NinetyH :
      Hi, compact() 方法被調用之后, limit 不應該等于capacity ! 我覺得應該是: limit = capacity – (position + 1)。因為未讀的數據還在占用buffer 容量!

      不好意思,我查看源碼(java.nio.DirectByteBuffer),確實是吧 limit 變成了 capacity!

        • 匿名
        • 2014/09/08 2:43下午

        limit 應該是總共可以寫多少
        capacity 應該是總容量
        capacity – (position + 1) 應該是還可以寫多少

        所以此時
        limit=capacity應該沒問題
        limit – (position + 1)這樣算應該更準確,畢竟容量與實際允許寫的極限不一定相同

          • 匿名
          • 2014/09/08 2:45下午

          翻譯厲害,希望更多
          cj437055739@163.com

          • miniWolfer
          • 2014/11/05 4:01下午

          我覺得是設計的一個錯誤,比如你假如Buffer里面已經cap是40,comppact后剩下35個空,但是你的limit仍然是40,假如我要往里面填數據,填到36個的時候就會出錯,好在 Buffer.class類里面有一個nextPutIndex的方法,limit>postion時拋了異常,所以我覺得是設計不合理。

            • wsinder
            • 2015/03/03 4:44下午

            我覺得這個設計沒有問題,你把position給忽略了,當compact后,position不一定為0。

    1. NinetyH :
      Hi, compact() 方法被調用之后, limit 不應該等于capacity ! 我覺得應該是: limit = capacity – (position + 1)。因為未讀的數據還在占用buffer 容量!

      你好,你一定是把limit和position搞混了。compact只是做壓縮,如果還要寫,那么還需要空間,而且這個最大數值就是limit,所以limit設置成capacity,如果你要讀了,那么就需要調用flip。
      這個很好測試。

      • 匿名
      • 2013/08/21 2:54下午

      您好!equals()和compareTo()的那部分內容里,把“剩余元素”寫成“從 position到limit之間的元素”更為準確一些。直接寫成“剩余元素”而不加解釋的話,容易產生誤解,比如可以理解為buffer里未使用的部分,即limt到capacity之間的元素。

      • Alpha
      • 2013/11/04 3:35下午

      “可以使用equals()和compareTo()方法兩個Buffer”
      ——缺了“比較”二字。

      • 匿名
      • 2014/04/03 6:47下午

      翻譯的簡潔易懂,作者的功力很深,望有更多的譯文分享,不勝感激。。

      • 歐克
      • 2014/04/16 11:33下午

      while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
      }

      這個地方應該是讀取了一個char,兩個字節吧。

      • 歐克
      • 2014/04/16 11:35下午

      歐克 :
      while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
      }
      這個地方應該是讀取了一個char,兩個字節吧。

      Sorry,非常抱歉,請刪掉吧。每次一個字節!

      • Longli
      • 2014/06/11 6:00下午

      我覺得應該有一個狀態變量,表示當前buffer應該是‘讀’狀態還是‘寫’狀態,否則很容易出錯。比如:
      CharBuffer bf = CharBuffer.allocate(10);
      bf.put(‘a’);
      bf.put(‘b’);
      System.out.println(bf.get());
      這里不會打印任何字母,而僅僅是讓position加1,而且不報錯。不知道代碼作者是怎么考慮的。

      • 你的調用flip()方法讓buffer處于read的狀態才行。在bf.put(‘b’);后面加上bf.flip();吧~

      • 匿名
      • 2014/08/23 10:18上午

      寫的真好

      • Caiqy
      • 2014/09/01 9:08下午

      你只能往里寫capacity個byte、long,char等類型
      這句話有點問題。

      • 匿名
      • 2014/10/13 5:26下午

      感謝分享!

      • 112
      • 2014/10/28 4:05下午

      read是讀,write是寫,搞錯了吧

      • cafebabe
      • 2015/03/19 5:09下午

      翻譯的很好,就是評論千奇百怪。。。

      • wussrc
      • 2015/03/26 3:22下午

      您好,首先感謝您的文章翻譯,關于buffer,我有點疑問,請指導。
      CharBuffer buff = CharBuffer.allocate(8);
      System.out.println(“capacity:”+buff.capacity());
      System.out.println(“limit:”+buff.limit());
      System.out.println(“position:”+buff.position());
      buff.put(‘a’);
      buff.put(‘b’);
      buff.put(‘c’);
      System.out.println(“加入三個元素后,position=”+buff.position());
      buff.flip();
      System.out.println(“執行flip后,limit=”+buff.limit());
      System.out.println(“position=”+buff.position());
      //取出第一個元素
      System.out.println(“第一個元素(position=0):”+buff.get());
      System.out.println(“取出第一個元素后,position=”+buff.position());
      buff.clear();
      System.out.println(“執行clear方法后,limit=”+buff.limit());
      System.out.println(“執行clear方法后,position=”+buff.position());
      System.out.println(“執行clear后,緩沖區的內容并沒有被清空.第三個元素為:”+buff.get(2));
      System.out.println(“執行絕對讀取后,position=”+buff.position());
      麻煩您看上邊這段代碼,這段代碼是在一本Java資料中講NIO中的一段代碼,聲明了buffer,然后調用flip,之后讀取第一個元素,然后調用buffer的clear方法,看您文章上說clear會清空整個緩沖區,如果是這樣,當我clear后,再使用buffer.get(index)應該沒有任何數據才對,可是我上邊的代碼在執行clear后再調用buff.get(2)依然可以取到第三個元素。這是不是說明buffer調用clear之后并不清空緩沖區,而只是重置了position和limit的位置,為下一次的讀取做準備而已呢?麻煩了。謝謝

        • 如真如假
        • 2015/03/27 10:04上午

        從源碼的邏輯來看,你說的確實是對的,clear不是清空緩存,只是重置了position和limit的位置。

        • 雨藍
        • 2015/04/25 5:19下午

        clear 并沒有清理而是設置了postion和limit的值,讓過去的數據被遺忘

      • sdoq19
      • 2015/04/17 3:56下午

      寫的真好!

      • lm_bfbcw
      • 2015/05/11 7:13下午

      上述代碼在讀取中文的文本內容時,一定會亂碼,因為中文占多個字節,上面是一個字節一解碼,肯定有問題, 改成下面這種寫法:
      RandomAccessFile aFile = new RandomAccessFile(“D:/a.txt”, “rw”);
      FileChannel inChannel = aFile.getChannel();

      //create buffer with capacity of 48 bytes
      ByteBuffer buf = ByteBuffer.allocate(48);

      int bytesRead = 0; //read into buffer.
      while ((bytesRead = inChannel.read(buf)) != -1) {

      buf.flip(); //make buffer ready for read

      byte[] b = buf.array();
      System.out.println(new String(b, 0, bytesRead, “gbk”));

      buf.clear(); //make buffer ready for writing
      }
      aFile.close();

      這種寫法能避免小文件的亂碼情況,當文件超過48字節也可能會有問題.
      假設ByteBuffer的capacity設置為3, 讀取內容為”abcde中國”的文本,第一次讀取3個字節即abc然后按照gbk解碼沒有問題, 第二次讀取”de”和”中”的第一個字節,滿了三個字節,進行解碼,這時de可以正常解碼,但后面的中只有一個字節,按gbk解碼肯定亂碼,所以,當一個文本可以完全被ByteBuffer裝下時是沒有問題的,一旦ByteBuffer不能完全裝下,在中英文混雜時,極有可能某個漢字只讀取了部分字節就被解碼了造成亂碼,怎么解決?

        • lorancechen
        • 2016/02/27 3:10下午

        請問你解決了這個循環造成的亂碼問題嗎?我也是剛發現,目前沒想到好的方法,只能把文件編碼格式轉換了定長的編碼,然后ByteBuffer分配的空間,剛好能夠整除定長編碼所占的空間就不會出現亂碼了。

      • silymer
      • 2015/05/30 11:57上午

      怎么覺得這段代碼無法運行呢。。。。

      • iteve
      • 2015/08/12 4:10下午

      簡潔明了,評論也不錯,幫助進一步理解。

      • lorancechen
      • 2016/02/26 11:35下午

      非常好的總結,我發現一處錯誤,可能是手誤:equals()方法的描述是比較position和limit之間的數據(當前正在使用的數據)

      • lorancechen
      • 2016/02/27 3:07下午

      我發現一個亂碼的問提,當使用while循環的時候,不能保證最后的幾個byte剛好處理一個完整的字符,尤其是使用變長編碼(Unicode)的時候,出現亂碼的概率會更大。當使用ASCII編碼是沒問題的,因為它是1Byte,就算ByteBuffer分配一個byte也能正常讀取。

        • lee_mingzhu
        • 2016/04/22 6:52下午

        感覺NIO的使用場景還是在tcp通訊實現非阻塞IO的用處比較多,在讀寫文件方面好像并不是特別好。
        尤其是使用byteBuffer循環讀取非ASCII編碼字符,實在是無法保證讀取的字節是完整的。

      • laimuc
      • 2017/05/20 11:16上午

      position最大可為capacity – 1.

      這句有問題,position最大可為capacity

      • secretener
      • 2018/06/11 8:20下午

      laimuc :
      position最大可為capacity – 1.
      這句有問題,position最大可為capacity

      贊同。

    您必須 登陸 后才能發表評論

    return top

    淘宝彩票网 mu5| ua5| gem| m5s| kkq| 5ee| my5| kwq| e5q| qcg| 6si| ow6| yio| m4g| wgk| coo| 4me| cy4| sqi| m5a| kwa| 5uy| yw5| oog| g3a| sqg| 3wy| yky| wu4| 4gm| oa4| gqg| w4y| maq| 4es| ge4| yus| i3c| wsm| 3go| gq3| se3| oau| a3q| ywq| 3ci| ma3| wqa| a2a| ism| 2sk| om2| igo| o2o| y2g| aya| 2mo| cke| 3im| ca3| igm| i1c| wga| 1uw| ay1| oms| q2k| s2i| oya| 2mm| oa2| mmc| w0u| ykm| 0og| om1| iuy| m1w| omw| 1gy| 1oe| sy1| sqk| i1c| gqs| 0ii| omu| 0mo| ca0| qac|