1、坑点介绍
如下代码:
ByteBuffer buffer = ByteBuffer.allocateDirect(int capacity)
byte[] array = buffer.array()
在android平台前面会有几个字节是没有实际数据的,在jre环境下,发生异常。 怎么理解呢? 来个栗子:
private void bufferTest() {
ByteBuffer buffer = ByteBuffer.allocateDirect(10);
buffer.put((byte) 1);
buffer.put((byte) 2);
byte[] arr = buffer.array();
for (int i = 0; i < 10; i++) {
Log.d("BufferTester", "bufferTest arr[" + i + "]=" + arr[i]);
}
}
结果如下:
2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[0]=0 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[1]=0 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[2]=0 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[3]=0 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[4]=1 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[5]=2 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[6]=0 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[7]=0 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[8]=0 2021-10-27 15:29:43.169 D/BufferTester: bufferTest arr[9]=0
通过上面的结果可以看到 arr[4]=1, arr[5]=2。我们是put了1和2,正常情况是从数组第一个元素开始写入,我们取出的数组却跳跃了前面4个字节。
2、正确使用姿势(入坑了怎么办)
实际上buffer.array() 是不能用的,我们需要使用get(byte[] dst) 或get(byte[] dst, int offset, int length) ,具体代码如下
private void bufferTest() {
ByteBuffer buffer = ByteBuffer.allocateDirect(10);
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.position(0);
byte []arr = new byte[10];
buffer.get(arr);
for (int i = 0; i < 10; i++) {
Log.d("BufferTester", "bufferTest arr[" + i + "]=" + arr[i]);
}
}
结果:
2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[0]=1 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[1]=2 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[2]=0 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[3]=0 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[4]=0 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[5]=0 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[6]=0 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[7]=0 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[8]=0 2021-10-27 15:53:49.954 D/BufferTester: bufferTest arr[9]=0
arr[0]=1,arr[1]=2 是我们期望的结果,也是正确结果。 注:put一字节,postion+1,所以上述代码在取的时候,将pos恢复到起始位置。 所以请使用get方法来获取内容。
如果还是要用array(),那么一定要完全掌握好DirectByteBuffer的机制。
3、坑坑详解
ByteBuffer是一个抽象类,通过类函数 allocateDirect(int capacity)创建的是DirectByteBuffer对象;通过类函数allocate(int capacity)创建的是HeapByteBuffer对象。关系和代码如下
HeapByteBuffer extends ByteBuffer
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
DirectByteBuffer extends MappedByteBuffer extends ByteBuffer
public static ByteBuffer allocateDirect(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
DirectByteBuffer.MemoryRef memoryRef = new DirectByteBuffer.MemoryRef(capacity);
return new DirectByteBuffer(capacity, memoryRef);
}
简单过一下:HeapByteBuffer 是堆上分配的空间,DirectByteBuffer是系统分配的空间。
3.1HeapByteBuffer可以用buffer.array()
private HeapByteBuffer(int cap, int lim, boolean isReadOnly) {
super(-1, 0, lim, cap, new byte[cap], 0);
this.isReadOnly = isReadOnly;
}
因为调用super的时候,就 按照给定的大小(new byte[cap]) 分配了数组传递给父类,最终调用array()的时候,就返回的这个数组。
3.2DirectByteBuffer的坑在哪里
注意了,我们重点看两个构造函数 DirectByteBuffer.MemoryRef memoryRef = new DirectByteBuffer.MemoryRef(capacity); return new DirectByteBuffer(capacity, memoryRef);
- MemoryRef内存分配
注意看注释要点1、2、3:
MemoryRef(int capacity) {
VMRuntime runtime = VMRuntime.getRuntime();
buffer = (byte[]) runtime.newNonMovableArray(byte.class, capacity + 7);
allocatedAddress = runtime.addressOf(buffer);
offset = (int) (((allocatedAddress + 7) & ~(long) 7) - allocatedAddress);
isAccessible = true;
isFreed = false;
originalBufferObject = null;
}
- DirectByteBuffer(int capacity, MemoryRef memoryRef)内存管理与使用
DirectByteBuffer(int capacity, MemoryRef memoryRef) {
super(-1, 0, capacity, capacity, memoryRef.buffer, memoryRef.offset);
this.memoryRef = memoryRef;
this.address = memoryRef.allocatedAddress + memoryRef.offset;
cleaner = null;
this.isReadOnly = false;
}
也许老铁已经看出了端倪,此处传递给父类的数组是capacity + 7长度的,其中address是数组的一个子集,举个例子,如果capacity = 10,offset = 5,那么buffer的长度是17,allocatedAddress 是buffer[0]的地址,address 就是buffer[5] 地址。
- put是自行实现的
public final ByteBuffer put(byte x) {
if (!memoryRef.isAccessible) {
throw new IllegalStateException("buffer is inaccessible");
}
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
put(ix(nextPutIndex()), x);
return this;
}
private ByteBuffer put(long a, byte x) {
Memory.pokeByte(a, x);
return this;
}
private long ix(int i) {
return address + i;
}
- get(byte[] dst, int dstOffset, int length) 也是通过addr来操作的
public ByteBuffer get(byte[] dst, int dstOffset, int length) {
if (!memoryRef.isAccessible) {
throw new IllegalStateException("buffer is inaccessible");
}
checkBounds(dstOffset, length, dst.length);
int pos = position();
int lim = limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (length > rem)
throw new BufferUnderflowException();
Memory.peekByteArray(ix(pos),
dst, dstOffset, length);
position = pos + length;
return this;
}
总体来说DirectByteBuffer分配了一个字节数组buffer 长度capacity+7,并计算出了一个小于7的偏移值x,再得到一个buffer的偏移地址,address = buffer+x。put、get都是通过address来存取的,因偏移,存取起始位置不一定是buffer[0],array() 返回的是buffer,因此可能存在前面x个字节没有值。
- buffer长度验证
private void bufferTest() {
ByteBuffer buffer = ByteBuffer.allocateDirect(10);
byte[] arr = buffer.array();
Log.d("BufferTester", "bufferTest len = " + arr.length);
参数给的10,buffer实际长度17:
2021-10-27 17:18:14.740 D/BufferTester: bufferTest len = 17
同时在jre的环境下,DirectByteBuffer的实现稍有区别,没有偏移,且array被调用会抛出异常。没有计算offset也没有持有数组对象,只通过一个地址进行存取。
|