HeapByteBuffer | 在jvm堆上面的一个buffer,底层的本质是一个数组 | 由于内容维护在jvm里,所以把内容写进buffer里速度会快些;并且,可以更容易回收 |
DirectByteBuffer | 底层的数据其实是维护在操作系统的内存中,而不是jvm里,DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据 | 跟外设(IO设备)打交道时会快很多,因为外设读取jvm堆里的数据时,不是直接读取的,而是把jvm里的数据读到一个内存块里,再在这个块里读取的,如果使用DirectByteBuffer,则可以省去这一步,实现zero copy |
ByteBuffer的属性
byte[] buff //buff即内部用于缓存的数组。 position //当前读取的位置。 mark //为某一读过的位置做标记,便于某些时候回退到该位置。 capacity //初始化时候的容量。是它所包含的元素的数量,缓冲区的容量不能为负并且不能更改 limit //当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。
这些属性总是满足以下条件: 0 <= mark <= position <= limit <= capacity
ByteBuffer的常规方法
ByteBuffer allocate(int capacity) //创建一个指定capacity的ByteBuffer。 ByteBuffer allocateDirect(int capacity) //创建一个direct的ByteBuffer,这样的ByteBuffer在参与IO操作时性能会更好 ByteBuffer wrap(byte [] array) ByteBuffer wrap(byte [] array, int offset, int length) //把一个byte数组或byte数组的一部分包装成ByteBuffer。 //get put方法不多说 byte get(int index) ByteBuffer put(byte b) int getInt() //从ByteBuffer中读出一个int值。 ByteBuffer putInt(int value) // 写入一个int值到ByteBuffer中。
ByteBuffer的特殊方法
Buffer clear() //把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。 Buffer flip() //把limit设为当前position,把position设为0,一般在从Buffer读出数据前调用。 Buffer rewind() //把position设为0,limit不变,一般在把数据重写入Buffer前调用。和 clear() 类似,只是不改动限制 compact() //将 position 与 limit之间的数据复制到buffer的开始位置,复制后 position = limit -position,limit = capacity, 但如 果position 与limit 之间没有数据的话发,就不会进行复制。 mark() & reset() //通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。
put
写模式下,往buffer里写一个字节,并把postion移动一位。写模式下,一般limit与capacity相等。
flip
写完数据,需要开始读的时候,将postion复位到0,并将limit设为当前postion。
get
从buffer里读一个字节,并把postion移动一位。上限是limit,即写入数据的最后位置。
clear
将position置为0,并不清除buffer内容。
mark & reset
mark相关的方法主要是mark()(标记)和reset()(回到标记).
/** * 1.txt => VipSoftAAA * * @throws Exception */ @Test public void readFile() throws Exception { FileChannel channel = FileChannel.open(Paths.get("D:\\temp\\1.txt"), StandardOpenOption.READ); ByteBuffer buf = ByteBuffer.allocate(7); while (channel.read(buf) != -1) { buf.flip(); String ss = new String(buf.array()); System.out.println(ss); // 这种方式的读取,不会产生 position 的变化 int idx = 0; while (buf.hasRemaining()) { /** * get() 后 position 会往后+1 * 如果执行完成后,没有 clear(),则新的文件内容不会被读出来。因为 position 已经到了 capacity 值了,没地方放新内容了 */ System.out.print((char) buf.get()); if (idx == 2) { /** * 将 position 与 limit之间的数据复制到buffer的开始位置 * 复制后,position = (pos <= lim ? lim - pos : 0) 接着往后读 * 所以第二次取出AAA时,重置后,position =0 ,这时候会就把buf里面的数据重新再打印一遍变成 AAAAAAtoft */ buf.compact(); String compactStr = new String(buf.array()); System.out.println("\r\nCompact => " + compactStr); // compact后一次性读出来看变化,发现Soft被复制到了前面 } idx++; } //第二次读出4个字节,但输出还是AAASoft,说明 clear 只是把 position 设为0, 并没有清除数据 buf.clear(); System.out.println(); } channel.close(); System.out.println(); } //输出结果: //VipSoft //buf.array() 一次性打印全部,无偏移 //Vipoft //compact=7-3=4,所以跳过第三位的S,从第4位开始取值 //AAAtoft //一次性打印全部,因为 compact 将后position~limit的数据复制到了开始位置,所以AAA后面多了个 t //AAAAAAtoft //第二次 pos>lim 所以 pos=0 把第一次的内容又打了一遍
拿第一个例子来分析,这三个属性的作用,capacity(容量)、limit(限制)和position(位置)
1.分配内存大小为10的缓存区。索引10的空间是我虚设出来,实际不存在,为了能明显表示capacity。IntBuffer的容量为10,所以capacity为10,在这里指向索引为10的空间。Buffer初始化的时候,limit和capacity指向同一索引。position指向0。
2.往Buffer里加一个数据。position位置移动,capacity不变,limit不变。
3.Buffer读完之后,往bufer里写了5个数据,position指向索引为5的第6个数据,capacity不变,limit不变。
4.执行flip()。这时候对照着,之前flip源码去看。把position的值赋给limit,所以limit=5,然后position=0。capacity不变。结果就是:
5.Buffer开始往外写数据。每写一个,position就下移一个位置,一直移到limit的位置,结束。
上图的顺序就是代码中的IntBuffer从初始化,到读数据,再写数据三个状态下,capacity,position,limit三个属性的变化和关系。
大家可以发现:
1. 0 <= position <= limit <= capacity
2. capacity始终不变
@Test public void nioTest() { // 分配内存大小为10的缓存区 IntBuffer buffer = IntBuffer.allocate(10); System.out.println("capacity:" + buffer.capacity()); for (int i = 0; i < 5; ++i) { System.out.println("put data position:" + buffer.position()); buffer.put(i+1); } System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(">>>>>>>> position:" + buffer.position()); System.out.println(">>>>>>>> limit:" + buffer.limit()); System.out.println(">>>>>>>> capacity:" + buffer.capacity()); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(">>>>> flip() 使缓冲区为一系列新的通道写入或相对获取 操作做好准备<<<<<"); buffer.flip(); System.out.println("after flip limit:" + buffer.limit()); System.out.println("enter while loop"); while (buffer.hasRemaining()) { System.out.println(">>>>>>>> position:" + buffer.position()); System.out.println(">>>>>>>> limit:" + buffer.limit()); System.out.println(">>>>>>>> capacity:" + buffer.capacity()); System.out.println(">>>>>>>> 元素:" + buffer.get()); } System.out.println(">>>>> rewind() 将位置 position = 0 也就是相当于选取当前缓冲区总的全部有效数据 和clear()类似,只是不改动限制 <<<<<"); buffer.rewind(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(">>>>>>>> position:" + buffer.position()); System.out.println(">>>>>>>> limit:" + buffer.limit()); System.out.println(">>>>>>>> capacity:" + buffer.capacity()); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(">>>>> clear() 用来初始化缓存空间,例如读取文件时将文件内容置入缓存时要先执行此方法 <<<<<"); buffer.clear(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(">>>>>>>> position:" + buffer.position()); System.out.println(">>>>>>>> limit:" + buffer.limit()); System.out.println(">>>>>>>> capacity:" + buffer.capacity()); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); }