ByteBuffer能夠做爲一個緩衝區,是由於它是內存中的一段連續的空間,在ByteBuffer對象內部定義了四個索引,分別是mark,position,limit,capacity。java
flip() 與 clear() 等方法都是經過操做Buffer類中定義的limit、position索引來控制讀寫的開始和結束位置。數組
position: 表示當前可讀寫的指針,若是是向ByteBuffer對象中寫入一個字節,那麼就會向position所指向的地址寫入這個字節,若是是從ByteBuffer讀出一個字節,那麼就會讀出position所指向的地址讀出這個字節,讀寫完成後,position加1緩存
limit: 能夠讀寫的邊界,當position到達limit時,就表示將ByteBuffer中的內容讀完,或者將ByteBuffer寫滿了。app
capacity是這個ByteBuffer的容量,capacity初始化後是不變。ide
ByteBuffer繼承自Buffer類,四個索引值、與操做索引值的方法也都定義在Buffer類中。函數
JDK1.8: 直接緩存區與 非直接緩存區 的區別post
<h2> Direct vs. non-direct buffers </h2>性能
<p> A byte buffer is either direct or non-direct. Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations. <直接緩衝區 在每次調用底層操做系統的一個本機I/O操做以前(或以後),它將嘗試避免將緩衝區的內容複製到(或從)中間緩衝區, 直接在該區域作IO操做>(零拷貝 - 快的緣由)測試
<p> A direct byte buffer may be created by invoking the { #allocateDirect(int) allocateDirect} factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance. <直接緩衝區一般比非直接緩衝區具備更高的分配和釋放成本。直接緩衝區的內容可能位於正常垃圾收集機制的堆內存以外,所以它們對應用程序內存佔用的影響可能不明顯。所以,建議直接緩衝區主要分配給受底層系統本機I/O操做影響的大型、長壽命緩衝區。通常來講,最好只在直接緩衝區在程序性能上產生可測量的收益時分配它們。>this
<p> A direct byte buffer may also be created by { java.nio.channels.FileChannel#map mapping} a region of a file directly into memory. An implementation of the Java platform may optionally support the creation of direct byte buffers from native code via JNI. If an instance of one of these kinds of buffers refers to an inaccessible region of memory then an attempt to access that region will not change the buffer's content and will cause an unspecified exception to be thrown either at the time of the access or at some later time. <一個Java平臺的實現能夠支持經過JNI從本機代碼建立直接字節緩衝區。若是一個此類緩衝區的實例引用了一個不可訪問的內存區域,那麼訪問該區域的嘗試不會更改緩衝區的內容,而且會致使在訪問時或稍後某個時間拋出未指定的異常>
<p> Whether a byte buffer is direct or non-direct may be determined by invoking its { #isDirect isDirect} method. This method is provided so that explicit buffer management can be done in performance-critical code.
limit(), limit(10)等 | 其中讀取和設置這4個屬性的方法的命名和jQuery中的val(),val(10)相似,一個負責get,一個負責set |
reset() | 把position設置成mark的值,至關於以前作過一個標記,如今要退回到以前標記的地方 |
clear() | position = 0;limit = capacity;mark = -1; 有點初始化的味道,可是並不影響底層byte數組的內容 |
flip() | limit = position;position = 0;mark = -1; 翻轉,也就是讓flip以後的position到limit這塊區域變成以前的0到position這塊,翻轉就是將一個處於存數據狀態的緩衝區變爲一個處於準備取數據的狀態 |
rewind() | 把position設爲0,mark設爲-1,不改變limit的值 |
remaining() | return limit - position;返回limit和position之間相對位置差 |
hasRemaining() | return position < limit返回是否還有未讀內容 |
compact() | 把從position到limit中的內容移到0到limit-position的區域內,position和limit的取值也分別變成limit-position、capacity。若是先將positon設置到limit,再compact,那麼至關於clear() |
get() | 相對讀,從position位置讀取一個byte,並將position+1,爲下次讀寫做準備 |
get(int index) | 絕對讀,讀取byteBuffer底層的bytes中下標爲index的byte,不改變position |
get(byte[] dst, int offset, int length) | 從position位置開始相對讀,讀length個byte,並寫入dst下標從offset到offset+length的區域 |
put(byte b) | 相對寫,向position的位置寫入一個byte,並將postion+1,爲下次讀寫做準備 |
put(int index, byte b) | 絕對寫,向byteBuffer底層的bytes中下標爲index的位置插入byte b,不改變position |
put(ByteBuffer src) | 用相對寫,把src中可讀的部分(也就是position到limit)寫入此byteBuffer |
put(byte[] src, int offset, int length) | 從src數組中的offset到offset+length區域讀取數據並使用相對寫寫入此byteBuffer |
package com.noob.learn.nio; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * https://my.oschina.net/u/3434392/blog/2999202 */ public class ByteBufferTest { public static void main(String[] args) throws IOException { System.out.println("before alocate: " + memoryLog()); /* // 若是分配的內存太小,調用Runtime.getRuntime().freeMemory()大小不會變化 ! 125761是這次測試的閥值 ByteBuffer buffer = ByteBuffer.allocate(125761); System.out.println("buffer = " + buffer); System.out.println("after alocate: " + memoryLog());*/ // 這部分直接用的系統內存,因此對JVM的內存沒有影響 ---只有單獨測試才能徹底符合設想 ByteBuffer directBuffer = ByteBuffer.allocateDirect(1736076288); System.out.println("directBuffer = " + directBuffer); System.out.println("after direct alocate: " + memoryLog()); System.out.println(String.format("初始化後:hasRemaining: %s", directBuffer.hasRemaining())); directBuffer.compact(); System.out.println(String.format("compact後:hasRemaining: %s", directBuffer.hasRemaining())); /* System.out.println("----------Test wrap--------"); byte[] bytes = new byte[32]; buffer = ByteBuffer.wrap(bytes); System.out.println(buffer); buffer = ByteBuffer.wrap(bytes, 10, 10); System.out.println(buffer);*/ FileChannel fin = null; FileChannel fout = null; try { fin = new FileInputStream("filein").getChannel(); fout = new FileOutputStream("fileout").getChannel(); while (fin.read(directBuffer) != -1) { directBuffer.flip(); fout.write(directBuffer); directBuffer.clear(); } } catch (FileNotFoundException e) { } finally { try { if (fin != null) { fin.close(); } if (fout != null) { fout.close(); } } catch (IOException e) { throw e; } } } private static String memoryLog() { return String.format("total:%s, max:%s, free:%s", Runtime.getRuntime().totalMemory(), Runtime.getRuntime() .maxMemory(), Runtime.getRuntime().freeMemory()); } }
驗證結果:
經過ByteBuffer.allocate()方法分配了一段內存空間,初始化時,position =0;limit =capacity; mark = -1
/** * Allocates a new byte buffer. * * <p> The new buffer's position will be zero, its limit will be its * capacity, its mark will be undefined, and each of its elements will be * initialized to zero. It will have a {@link #array backing array}, * and its {@link #arrayOffset array offset} will be zero. * * @param capacity * The new buffer's capacity, in bytes * * @return The new byte buffer * * @throws IllegalArgumentException * If the <tt>capacity</tt> is a negative integer */ public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); }
ByteBuffer類提供了4個靜態工廠方法來得到ByteBuffer的實例:
allocate(int capacity) | 從堆空間中分配一個容量大小爲capacity的byte數組做爲緩衝區的byte數據存儲器.默認是heapByteBuffer. |
allocateDirect(int capacity) | 不使用JVM堆棧而是經過操做系統來建立內存塊用做緩衝區,它與當前操做系統可以更好的耦合,所以能進一步提升I/O操做速度。可是分配直接緩衝區的系統開銷很大,所以只有在緩衝區較大並長期存在,或者須要常常重用時,才使用這種緩衝區 |
wrap(byte[] array) | 這個緩衝區的數據會存放在byte數組中,bytes數組或buff緩衝區任何一方中數據的改動都會影響另外一方。其實ByteBuffer底層原本就有一個bytes數組負責來保存buffer緩衝區中的數據,經過allocate方法系統會幫你構造一個byte數組 |
wrap(byte[] array, int offset, int length) |
在上一個方法的基礎上能夠指定偏移量和長度,這個offset也就是包裝後byteBuffer的position,而length呢就是limit-position的大小,從而咱們能夠獲得limit的位置爲length+position(offset) |
在DirectByteBuffer的實現中經過sun.misc.Unsafe直接在 物理內存中建立緩衝區。
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
總結: DirectByteBuffer比較適合重複使用直接緩衝區並屢次讀寫的操做。 HeapByteBuffer比較適合建立新的緩衝區,而且重複讀寫不會太多的場景。
翻轉就是將一個處於存數據狀態的緩衝區變爲一個處於準備取數據的狀態.。之因此調用ByteBuffer.flip()方法是由於在向ByteBuffer寫入數據後,position爲緩衝區中剛剛讀入的數據的最後一個字節的位置,flip方法將limit值置爲position值,position置0,這樣在調用get*()方法從ByteBuffer中取數據時就能夠取到ByteBuffer中的有效數據。
/** * Flips this buffer. The limit is set to the current position and then * the position is set to zero. If the mark is defined then it is * discarded. * * <p> After a sequence of channel-read or <i>put</i> operations, invoke * this method to prepare for a sequence of channel-write or relative * <i>get</i> operations. For example: * * <blockquote><pre> * buf.put(magic); // Prepend header * in.read(buf); // Read data into rest of buffer * buf.flip(); // Flip buffer * out.write(buf); // Write header + data to channel</pre></blockquote> * * <p> This method is often used in conjunction with the {@link * java.nio.ByteBuffer#compact compact} method when transferring data from * one place to another. </p> * * @return This buffer */ public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
在調用 fout.write(buff) 時,就將buff緩衝區中的數據寫入到輸出管道,此時調用ByteBuffer.clear()方法爲下次從管道中讀取數據作準備,可是調用clear方法並不將緩衝區的數據清空,而是重置position,mark,limit這三個變量的值:
/** * Clears this buffer. The position is set to zero, the limit is set to * the capacity, and the mark is discarded. * * <p> Invoke this method before using a sequence of channel-read or * <i>put</i> operations to fill this buffer. For example: * * <blockquote><pre> * buf.clear(); // Prepare buffer for reading * in.read(buf); // Read data</pre></blockquote> * * <p> This method does not actually erase the data in the buffer, but it * is named as if it did because it will most often be used in situations * in which that might as well be the case. </p> * * @return This buffer */ public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
若是不清空的話,假設這次該ByteBuffer中的數據是滿的,下次讀取的數據不足以填滿緩衝區(read是從0開始),那麼就會存在上一次已經處理的的數據,因此在判斷緩衝區中是否還有可用數據時,使用ByteBuffer.hasRemaining()方法,
/** * Returns the number of elements between the current position and the * limit. * * @return The number of elements remaining in this buffer */ public final int remaining() { return limit - position; } /** * Tells whether there are any elements between the current position and * the limit. * * @return <tt>true</tt> if, and only if, there is at least one element * remaining in this buffer */ public final boolean hasRemaining() { return position < limit; }
eg. 剛剛建立一個ByteBuffer對象buff時,position=0,limit=capacity,那麼此時調用buff.hasRemaining()則會返回true,這樣來判斷緩衝區中是否有數據是不行的,由於此時緩衝區中的存儲的所有是0,可是調用一次compact()方法就能夠將position置爲limit值,這樣再經過buff.hasRemaining()就會返回 false。
把從position到limit中的內容移到0到limit-position的區域內,position和limit的取值也分別變成limit-position、capacity。若是先將positon設置到limit,再compact,那麼至關於clear()。
對於ByteBuffer,其子類HeapByteBuffer的compact方法實現是這樣的:
public ByteBuffer compact() { System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); position(remaining()); limit(capacity()); discardMark(); return this; } protected int ix(int i) { return i + offset; }
第一條語句做用是將數組hb當前position所指向的位置開始複製長度爲limit-position的數據到hb數組的開始ix(0),其中使用到了ix()函數,這個函數是將參數值加上一個offset值,offset即一個偏移值。
eg. 對於一個很大的緩衝區,將其分紅兩段,第一段的起始位置是p1,長度是q1, 第二段起始位置是p2,長度是q2,那麼分別將這兩段包裝成一個HeapByteBuffer對象,而後這兩個HeapByteBuffer對象(ByteBuffer的子類,默認實現)的offset屬性分別設置爲p1和p2,這樣就能夠經過在內部使用ix()函數來簡化ByteBuffer對外提供的接口,在使用者看來,與默認的ByteBuffer並無區別。
在compact函數中,接着將當前的緩衝區的position索引置爲 limit - position,limit索引置爲緩衝區的容量,這樣調用compact方法中就能夠將緩衝區的有效數據所有移到緩衝區的首部,而position指向下一個可寫位置。