ByteBuffer 在平时工作中可能用到的不多,但是面试中经常会被问到。本文总结了一些关于 ByteBuffer 的用法和相关 API 介绍。
下面是创建ByteBuffer对象的几种方式:
|
allocate |
|
allocateDirect |
|
wrap |
|
wrap |
allocate方式创建的ByteBuffer对象我们称之为非直接缓冲区,这个ByteBuffer对象(和对象包含的缓冲数组)都位于JVM的堆区。wrap方式和allocate方式创建的ByteBuffer没有本质区别,都创建的是非直接缓冲区。
allocateDirect方法创建的ByteBuffer我们称之为直接缓冲区,此时ByteBuffer对象本身在堆区,而缓冲数组位于非堆区, ByteBuffer对象内部存储了这个非堆缓冲数组的地址。在非堆区的缓冲数组可以通过JNI(内部还是系统调用)方式进行IO操作,JNI不受gc影响,机器码执行速度也比较快,同时还避免了JVM堆区与操作系统内核缓冲区的数据拷贝,所以IO速度比非直接缓冲区快。然而allocateDirect方式创建ByteBuffer对象花费的时间和回收该对象花费的时间比较多,所以这个方法适用于创建那些需要重复使用的缓冲区对象。
ByteBuffer 属性和方法
ByteBuffer对象三个重要属性 position, limit和capacity。其中capacity表示了缓冲区的总容量,始终保持不变,初始时候position 等于 0 , limit 等于 capacity。
put:向缓冲区放入数据
|
put |
ByteBuffer |
put |
ByteBuffer |
put |
调用put方法前,limit应该等于capacity,如果不等于,几乎可以肯定我们对缓冲区的操作有误。在put方法中0到position-1的区域表示有效数据,position到limit之间区域表示空闲区域。put方法会从position的当前位置放入数据,每放入一个数据position增加1,当position等于limit(即空闲区域使用完)时还继续放入数据就会抛出BufferUnderflowException异常
get:从缓冲区读取数据
|
get |
ByteBuffer |
get |
ByteBuffer |
get |
在get方法中, 0到position-1的区域表示已读数据,position到limit之间的区域表示未读取的数据。每读取一个数据position增加1,当position等于limit时继续读取数据就会抛出BufferUnderflowException异常。
flip :将写模式转换成读模式
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
clear:清空缓冲区,将读模式转换写模式
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
compact:保留未读取的数据,将读模式转换写模式
public ByteBuffer compact() { int pos = position(); int lim = limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); unsafe.copyMemory(ix(pos), ix(0), (long)rem << 0); position(rem); limit(capacity()); discardMark(); return this; }
mark:保存当前position的位置到mark变量
public final Buffer mark() { mark = position; return this; }
rest:将position置为mark变量中的值
public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
mark方法和rest方法联合使用可实现从指定位置的重读。
rewind:从头开始重读
public final Buffer rewind() { position = 0; mark = -1; return this; }
ByteBuffer对象使用时又很多需要注意的地方,自认为这个API设计的不是很友好。比如一定不能连续两次调用flip和compact方法,flip方法调用以后不能再调用put方法,等等。要避免这些错误,只能在使用ByteBuffer前弄清楚当前缓冲区中0到position-1以及position到limit中数据表示的含义,这才是避免bug的根本办法。
从上面的介绍中我们可以看出,ByteBuffer对象既可以读,也可以写。除非我们能保证在读操作一次性使用完ByteBuffer对象中的所有数据,并且保证写入ByteBuffer对象向中的内容全部写入完成,否则同时用于读写的ByteBuffer对象会造成数据的混乱和错误。一般来说,我们都会创建两个ByteBuffer对象向,一个用于接收数据,另一个用于发送数据。
ByteBuffer是面向字节的,为方便基本数据类型的读取,ByteBuffer中还提供getInt,putInt,getFloat,putFloat等方法,这些方法方便我们在缓冲区存取单个基本数据类型。如果需要从基本数据类型数组中写入到ByteBuffer中,或者从ByteBuffer中读取到基本数据类型的数组中,那么我们可以通过已创建好的ByteBuffer对象的asXxxBuffer方法创建基本数据类型的Buffer。
|
asCharBuffer |
|
asDoubleBuffer |
|
asFloatBuffer |
|
asIntBuffer |
|
asLongBuffer |
假设有如下代码
IntBuffer intBufferObj = byteBufferObj.asIntBuffer();
此时intBufferObj和byteBufferObj对象共享底层的数组。但是比较坑爹的是两个buffer的position,limit是独立的,这样极易产生bug,需要引起我们注意。
: » ByteBuffer 直接缓冲区和非直接缓冲区
原创文章,作者:306829225,如若转载,请注明出处:https://blog.ytso.com/251751.html