天天看點

小師妹學JavaIO之:Buffer和Buff

文章目錄

  • 簡介
  • Buffer是什麼
  • Buffer進階
  • 建立Buffer
  • Direct VS non-Direct
  • Buffer的日常操作
    • 向Buffer寫資料
    • 從Buffer讀資料
    • rewind Buffer
    • Compact Buffer
    • duplicate Buffer
  • 總結

小師妹在學習NIO的路上越走越遠,唯一能夠幫到她的就是在她需要的時候給她以全力的支援。什麼都不說了,今天介紹的是NIO的基礎Buffer。老鐵給我上個Buff。

小師妹:F師兄,這個Buffer是我們縱橫王者峽谷中那句:老鐵給我加個Buff的意思嗎?

當然不是了,此Buffer非彼Buff,Buffer是NIO的基礎,沒有Buffer就沒有NIO,沒有Buffer就沒有今天的java。

因為NIO是按Block來讀取資料的,這個一個Block就可以看做是一個Buffer。我們在Buffer中存儲要讀取的資料和要寫入的資料,通過Buffer來提高讀取和寫入的效率。

更多精彩内容且看:

  • 區塊鍊從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特币等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程式員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程
更多内容請通路www.flydean.com

還記得java對象的底層存儲機關是什麼嗎?

小師妹:這個我知道,java對象的底層存儲機關是位元組Byte。

對,我們看下Buffer的繼承圖:

小師妹學JavaIO之:Buffer和Buff

Buffer是一個接口,它下面有諸多實作,包括最基本的ByteBuffer和其他的基本類型封裝的其他Buffer。

小師妹:F師兄,有ByteBuffer不就夠了嗎?還要其他的類型Buffer做什麼?

小師妹,山珍再好,也有吃膩的時候,偶爾也要換個蘿蔔白菜啥的,你以為乾隆下江南都幹了些啥?

ByteBuffer雖然好用,但是它畢竟是最小的機關,在它之上我們還有Char,int,Double,Short等等基礎類型,為了簡單起見,我們也給他們都搞一套Buffer。

小師妹:F師兄,既然Buffer是這些基礎類型的集合,為什麼不直接用結合來表示呢?給他們封裝成一個對象,好像有點多餘。

我們既然在面向對象的世界,從表面來看自然是使用Object比較合乎情理,從底層的本質上看,這些封裝的Buffer包含了一些額外的中繼資料資訊,并且還提供了一些意想不到的功能。

上圖列出了Buffer中的幾個關鍵的概念,分别是Capacity,Limit,Position和Mark。Buffer底層的本質是數組,我們以ByteBuffer為例,它的底層是:

final byte[] hb;      
  • Capacity表示的是該Buffer能夠承載元素的最大數目,這個是在Buffer建立初期就設定的,不可以被改變。
  • Limit表示的Buffer中可以被通路的元素個數,也就是說Buffer中存活的元素個數。
  • Position表示的是下一個可以被通路元素的index,可以通過put和get方法進行自動更新。
  • Mark表示的是曆史index,當我們調用mark方法的時候,會把設定Mark為目前的position,通過調用reset方法把Mark的值恢複到position中。

小師妹:F師兄呀,這麼多Buffer建立起來是不是很麻煩?有沒有什麼快捷的使用辦法?

一般來說建立Buffer有兩種方法,一種叫做allocate,一種叫做wrap。

public void createBuffer(){IntBuffer intBuffer= IntBuffer.allocate(10);log.info("{}",intBuffer);log.info("{}",intBuffer.hasArray());int[] intArray=new int[10];IntBuffer intBuffer2= IntBuffer.wrap(intArray);log.info("{}",intBuffer2);IntBuffer intBuffer3= IntBuffer.wrap(intArray,2,5);log.info("{}",intBuffer3);intBuffer3.clear();log.info("{}",intBuffer3);log.info("{}",intBuffer3.hasArray());}      

allocate可以為Buffer配置設定一個空間,wrap同樣為Buffer配置設定一個空間,不同的是這個空間背後的數組是自定義的,wrap還支援三個參數的方法,後面兩個參數分别是offset和length。

INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=0 lim=10 cap=10]INFO com.flydean.BufferUsage - trueINFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=0 lim=10 cap=10]INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=2 lim=7 cap=10]INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=0 lim=10 cap=10]INFO com.flydean.BufferUsage - true      

hasArray用來判斷該Buffer的底層是不是數組實作的,可以看到,不管是wrap還是allocate,其底層都是數組。

需要注意的一點,最後,我們調用了clear方法,clear方法調用之後,我們發現Buffer的position和limit都被重置了。這說明wrap的三個參數方法設定的隻是初始值,可以被重置。

小師妹:F師兄,你說了兩種建立Buffer的方法,但是兩種Buffer的背景都是數組,難道還有非數組的Buffer嗎?

自然是有的,但是隻有ByteBuffer有。ByteBuffer有一個allocateDirect方法,可以配置設定Direct Buffer。

小師妹:Direct和非Direct有什麼差別呢?

Direct Buffer就是說,不需要在使用者空間再複制拷貝一份資料,直接在虛拟位址映射空間中進行操作。這叫Direct。這樣做的好處就是快。缺點就是在配置設定和銷毀的時候會占用更多的資源,并且因為Direct Buffer不在使用者空間之内,是以也不受垃圾回收機制的管轄。

是以通常來說隻有在資料量比較大,生命周期比較長的資料來使用Direct Buffer。

看下代碼:

public void createByteBuffer() throws IOException {ByteBuffer byteBuffer= ByteBuffer.allocateDirect(10);log.info("{}",byteBuffer);log.info("{}",byteBuffer.hasArray());log.info("{}",byteBuffer.isDirect());try (RandomAccessFile aFile = new RandomAccessFile("src/main/resources/www.flydean.com", "r"); FileChannel inChannel = aFile.getChannel()) {MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());log.info("{}",buffer);log.info("{}",buffer.hasArray());log.info("{}",buffer.isDirect());}}      

除了allocateDirect,使用FileChannel的map方法也可以得到一個Direct的MappedByteBuffer。

上面的例子輸出結果:

INFO com.flydean.BufferUsage - java.nio.DirectByteBuffer[pos=0 lim=10 cap=10]INFO com.flydean.BufferUsage - falseINFO com.flydean.BufferUsage - trueINFO com.flydean.BufferUsage - java.nio.DirectByteBufferR[pos=0 lim=0 cap=0]INFO com.flydean.BufferUsage - falseINFO com.flydean.BufferUsage - true      

小師妹:F師兄,看起來Buffer确實有那麼一點複雜,那麼Buffer都有哪些操作呢?

Buffer的操作有很多,下面我們一一來講解。

向Buffer寫資料可以調用Buffer的put方法:

public void putBuffer(){IntBuffer intBuffer= IntBuffer.allocate(10);intBuffer.put(1).put(2).put(3);log.info("{}",intBuffer.array());intBuffer.put(0,4);log.info("{}",intBuffer.array());}      

因為put方法傳回的還是一個IntBuffer類,是以Buffer的put方法可以像Stream那樣連寫。

同時,我們還可以指定put在什麼位置。上面的代碼輸出:

INFO com.flydean.BufferUsage - [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]INFO com.flydean.BufferUsage - [4, 2, 3, 0, 0, 0, 0, 0, 0, 0]      

讀資料使用get方法,但是在get方法之前我們需要調用flip方法。

flip方法是做什麼用的呢?上面講到Buffer有個position和limit字段,position會随着get或者put的方法自動指向後面一個元素,而limit表示的是該Buffer中有多少可用元素。

如果我們要讀取Buffer的值則會從positon開始到limit結束:

public void getBuffer(){IntBuffer intBuffer= IntBuffer.allocate(10);intBuffer.put(1).put(2).put(3);intBuffer.flip();while (intBuffer.hasRemaining()) {log.info("{}",intBuffer.get());}intBuffer.clear();}      

可以通過hasRemaining來判斷是否還有下一個元素。通過調用clear來清除Buffer,以供下次使用。

rewind和flip很類似,不同之處在于rewind不會改變limit的值,隻會将position重置為0。

public void rewindBuffer(){IntBuffer intBuffer= IntBuffer.allocate(10);intBuffer.put(1).put(2).put(3);log.info("{}",intBuffer);intBuffer.rewind();log.info("{}",intBuffer);}      

上面的結果輸出:

INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=3 lim=10 cap=10]INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=0 lim=10 cap=10]      

Buffer還有一個compact方法,顧名思義compact就是壓縮的意思,就是把Buffer從目前position到limit的值指派到position為0的位置:

public void useCompact(){IntBuffer intBuffer= IntBuffer.allocate(10);intBuffer.put(1).put(2).put(3);intBuffer.flip();log.info("{}",intBuffer);intBuffer.get();intBuffer.compact();log.info("{}",intBuffer);log.info("{}",intBuffer.array());}      

上面代碼輸出:

INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=0 lim=3 cap=10]INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=2 lim=10 cap=10]INFO com.flydean.BufferUsage - [2, 3, 3, 0, 0, 0, 0, 0, 0, 0]      

最後我們講一下複制Buffer,有三種方法,duplicate,asReadOnlyBuffer,和slice。

duplicate就是拷貝原Buffer的position,limit和mark,它和原Buffer是共享原始資料的。是以修改了duplicate之後的Buffer也會同時修改原Buffer。

如果用asReadOnlyBuffer就不允許拷貝之後的Buffer進行修改。

slice也是readOnly的,不過它拷貝的是從原Buffer的position到limit-position之間的部分。

public void duplicateBuffer(){IntBuffer intBuffer= IntBuffer.allocate(10);intBuffer.put(1).put(2).put(3);log.info("{}",intBuffer);IntBuffer duplicateBuffer=intBuffer.duplicate();log.info("{}",duplicateBuffer);IntBuffer readOnlyBuffer=intBuffer.asReadOnlyBuffer();log.info("{}",readOnlyBuffer);IntBuffer sliceBuffer=intBuffer.slice();log.info("{}",sliceBuffer);}      

輸出結果:

INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=3 lim=10 cap=10]INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=3 lim=10 cap=10]INFO com.flydean.BufferUsage - java.nio.HeapIntBufferR[pos=3 lim=10 cap=10]INFO com.flydean.BufferUsage - java.nio.HeapIntBuffer[pos=0 lim=7 cap=7]      

繼續閱讀