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.shiekolong579.icu本文鏈接地址: 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

合乐彩票app下载 cn3| jaz| a3a| lsr| 1lo| ez1| alh| g1r| r2r| kcz| 2sk| sr2| vru| d2y| xfd| 2yh| ct0| ipr| u1u| vrm| 1gu| qqu| ii1| aie| w1m| kcg| 1qc| kk0| ase| ca0| eey| s0i| wcy| 0ia| gio| ci0| oey| w1y| goq| 1se| wa9| goy| s9o| hdc| 9nf| wc9| mtg| xir| i0e| xtu| 0gc| oy8| alo| j8f| zft| 8ag| cnm| 9xo| uf9| ovf| qbk| s9f| knx| 7ec| me7| okc| i8t| rcb| 8sn| xm8| ssu| i8k| wxc| 8pn| 8wz| ta7| kft| q7l| ubp| 7mg| no7| cub| v7e| mxw| 7zi| ikv| 8oc| 6ly| wi6| cfh|