IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android坑点-ByteBuffer.array() 入过坑吗 -> 正文阅读

[移动开发]Android坑点-ByteBuffer.array() 入过坑吗

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();//这里使用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);
//        byte[] arr = buffer.array();
        buffer.position(0);
        byte []arr = new byte[10];//put 2次,实际是2个字节
        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对象。关系和代码如下

  1. HeapByteBuffer extends ByteBuffer

    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
  1. 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);

  1. MemoryRef内存分配
    注意看注释要点1、2、3:
  MemoryRef(int capacity) {
       VMRuntime runtime = VMRuntime.getRuntime();
       
       //要点1,分配了一个字节数组,大小是我们给的大小+ 7,例如我们给10,分配了17字节
       buffer = (byte[]) runtime.newNonMovableArray(byte.class, capacity + 7);
       
       //要点2,得到buffer的地址(指针)
       allocatedAddress = runtime.addressOf(buffer);
       
       // Offset is set to handle the alignment: http://b/16449607
       //要点3、计算一个偏移量,计算结果是在0-7之间。
       offset = (int) (((allocatedAddress + 7) & ~(long) 7) - allocatedAddress);
       isAccessible = true;
       isFreed = false;
       originalBufferObject = null;
  }
  1. DirectByteBuffer(int capacity, MemoryRef memoryRef)内存管理与使用
DirectByteBuffer(int capacity, MemoryRef memoryRef) {
   //要点4、将要点1的数组和要点3的偏移传递给父类
   super(-1, 0, capacity, capacity, memoryRef.buffer, memoryRef.offset);
   this.memoryRef = memoryRef;

	//要点5、给address赋值为要点2的地址+要点3的偏移地址
   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] 地址。

  1. 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) {
//这里是重点,新加入的字节,用address+已经存在的长度所在的位置进行填充数据。
        return address + i;
    }
  1. 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();
            
        //通过address来取数据
        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个字节没有值。

  1. 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也没有持有数组对象,只通过一个地址进行存取。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-28 12:30:08  更:2021-10-28 12:30:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:11:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码