NIO之ByteBuffer的使用
NIO中有三大组件:Channel、Selector、Buffer,今天就来讲讲Buffer的使用。
Buffer的继承结构:
Buffer为java中的每个数据类型都提供了对应的实现类,但是实际使用中用得最多的还是ByteBuffer。
ByteBuffer的创建
在堆上创建:
log.info("freeMemory:{}MB", Runtime.getRuntime().freeMemory() / _1_MB);
ByteBuffer allocate = ByteBuffer.allocate(20 * _1_MB); // 堆上内存分配
log.info("freeMemory:{}MB", Runtime.getRuntime().freeMemory() / _1_MB);
运行结果如下:
2020-11-04 15:49:01,421 INFO [main] (ByteBufferNewDemo.java:20) - freeMemory:230MB
2020-11-04 15:49:01,436 INFO [main] (ByteBufferNewDemo.java:23) - freeMemory:209MB
在直接内存上创建:
OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
log.info("freeMemory:{}MB", osmxb.getFreePhysicalMemorySize() / _1_MB);
final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(20 * _1_MB);
log.info("freeMemory:{}MB", osmxb.getFreePhysicalMemorySize() / _1_MB);
运行结果如下:
2020-11-04 15:53:38,808 INFO [main] (ByteBufferNewDemo.java:35) - freeMemory:4524MB
2020-11-04 15:53:38,816 INFO [main] (ByteBufferNewDemo.java:37) - freeMemory:4503MB
ByteBuffer的使用
ByteBuffer有三个重要的属性:
- capacity:ByteBuffer底层其实是一个字节数组,capacity代表数组的长度。
- position:表示下一个要操作(读或写)的元素的位置。
- limit:在写模式下,limit=capacity,在读模式下,limit代表数组中实际元素的个数。
get和put值
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
byteBuffer.put((byte) 'a');
byteBuffer.put((byte) 'b');
byteBuffer.put((byte) 'c');
byteBuffer.put((byte) 'd');
log.info("put a b c d, {}", byteBuffer);
byteBuffer.flip(); // 切换为读模式
log.info("after flip, {}", byteBuffer);
log.info("get byte: {}", (char) byteBuffer.get());
log.info("after get, {}", byteBuffer);
运行结果如下:
2020-11-04 16:04:08,130 INFO [main] (ByteBufferNewDemo.java:47) - put a b c d, java.nio.HeapByteBuffer[pos=4 lim=8 cap=8]
2020-11-04 16:04:08,133 INFO [main] (ByteBufferNewDemo.java:49) - after flip, java.nio.HeapByteBuffer[pos=0 lim=4 cap=8]
2020-11-04 16:04:08,133 INFO [main] (ByteBufferNewDemo.java:50) - get byte: a
2020-11-04 16:04:08,133 INFO [main] (ByteBufferNewDemo.java:51) - after get, java.nio.HeapByteBuffer[pos=1 lim=4 cap=8]
一次性get
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
byteBuffer.put((byte) 'a');
byteBuffer.put((byte) 'b');
byteBuffer.put((byte) 'c');
byteBuffer.put((byte) 'd');
byteBuffer.flip(); // 切换为读模式
byte[] bytes = new byte[byteBuffer.remaining()];
log.info("before get, {}", byteBuffer);
byteBuffer.get(bytes, 0, bytes.length);
log.info("bytes:{}", new String(bytes));
log.info("after get, {}", byteBuffer);
运行结果如下:
2020-11-04 16:32:36,032 INFO [main] (ByteBufferNewDemo.java:64) - before get, java.nio.HeapByteBuffer[pos=0 lim=4 cap=8]
2020-11-04 16:32:36,034 INFO [main] (ByteBufferNewDemo.java:66) - bytes:abcd
2020-11-04 16:32:36,034 INFO [main] (ByteBufferNewDemo.java:67) - after get, java.nio.HeapByteBuffer[pos=4 lim=4 cap=8]
指定位置读和写
使用put和get方法时,指定index进行操作,不会改变position的位置。
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
byteBuffer.put(0, (byte) 'a');
byteBuffer.put(1, (byte) 'b');
byteBuffer.put(2, (byte) 'c');
byteBuffer.put(3, (byte) 'd');
log.info("put a b c d, {}", byteBuffer);
log.info("get byte: {}", (char) byteBuffer.get(0));
log.info("after get, {}", byteBuffer);
运行结果如下:
2020-11-04 16:35:23,152 INFO [main] (ByteBufferNewDemo.java:77) - put a b c d, java.nio.HeapByteBuffer[pos=0 lim=8 cap=8]
2020-11-04 16:35:23,154 INFO [main] (ByteBufferNewDemo.java:78) - get byte: a
2020-11-04 16:35:23,154 INFO [main] (ByteBufferNewDemo.java:79) - after get, java.nio.HeapByteBuffer[pos=0 lim=8 cap=8]
clear
clear()方法会加将position设置为0,limit设置成capacity,也就是说ByteBuffer被清空了,其实Buffer中的数据并未被清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。
ByteBuffer byteBuffer = ByteBuffer.allocate(32);
byteBuffer.put((byte) 'x');
log.info("before clear: {}" + byteBuffer);
byteBuffer.clear();
log.info("after clear: {}" + byteBuffer);
运行结果如下:
2020-11-04 16:44:36,956 INFO [main] (ByteBufferNewDemo.java:86) - before clear: {}java.nio.HeapByteBuffer[pos=1 lim=32 cap=32]
2020-11-04 16:44:36,958 INFO [main] (ByteBufferNewDemo.java:88) - after clear: {}java.nio.HeapByteBuffer[pos=0 lim=32 cap=32]
compact
compact()方法将所有未读的数据拷贝到Buffer起始处(压缩),然后将position设到最后一个未读元素正后面,limit属性依然像clear()方法一样,设置成capacity。
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
byteBuffer.put((byte) 'a');
byteBuffer.put((byte) 'b');
byteBuffer.put((byte) 'c');
byteBuffer.put((byte) 'd');
byteBuffer.flip(); // 切换为读模式
byteBuffer.get();
log.info("before compact: {}", byteBuffer);
byteBuffer.compact();
log.info("after compact: {}", byteBuffer);
byteBuffer.flip(); // 切换为读模式
log.info("{}",byteBuffer.remaining());
运行结果如下:
2020-11-04 16:51:19,010 INFO [main] (ByteBufferNewDemo.java:100) - before compact: java.nio.HeapByteBuffer[pos=1 lim=4 cap=8]
2020-11-04 16:51:19,013 INFO [main] (ByteBufferNewDemo.java:102) - after compact: java.nio.HeapByteBuffer[pos=3 lim=8 cap=8]
2020-11-04 16:51:19,013 INFO [main] (ByteBufferNewDemo.java:104) - 3
rewind
rewind()方法会将position设置为0,limit的值不变,类似磁带的倒带功能。
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
byteBuffer.position(10);
byteBuffer.limit(15);
log.info("before rewind: {}", byteBuffer);
byteBuffer.rewind();
log.info("after rewind: {}", byteBuffer);
运行结果如下:
2020-11-04 16:56:53,935 INFO [main] (ByteBufferNewDemo.java:112) - before rewind: java.nio.HeapByteBuffer[pos=10 lim=15 cap=16]
2020-11-04 16:56:53,939 INFO [main] (ByteBufferNewDemo.java:114) - after rewind: java.nio.HeapByteBuffer[pos=0 lim=15 cap=16]
mark和reset
ByteBuffer中有一个属性用来存放临时的位置下标,调用mark()会将mark设为当前的position的值,以后调用reset()会将position属性设置为mark的值。
ByteBuffer中的几个属性总是满足以下关系:0 <= mark <= position <= limit <= capacity
。
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
byteBuffer.position(10);
byteBuffer.mark();
byteBuffer.position(15);
log.info("before reset: {}", byteBuffer);
byteBuffer.reset();
log.info("after reset: {}", byteBuffer);
运行结果如下:
2020-11-04 17:00:13,572 INFO [main] (ByteBufferNewDemo.java:123) - before reset: java.nio.HeapByteBuffer[pos=15 lim=16 cap=16]
2020-11-04 17:00:13,577 INFO [main] (ByteBufferNewDemo.java:125) - after reset: java.nio.HeapByteBuffer[pos=10 lim=16 cap=16]