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指向下一个可写位置。