《计算器》二、ArrayBuffer使用指南

Class (ArrayBuffer) 使用指南

效果

一、前言

在HarmonyOS应用开发中,处理二进制数据是一项基础且重要的能力。ArrayBuffer是ArkTS中用于表示内存中一段固定大小的连续二进制数据的核心类型,它是所有类型化数组(TypedArray)和数据视图(DataView)的底层基础。

本文将从概念讲起,逐步深入到ArrayBuffer的实际应用,并通过一个完整的"二进制消息协议解析器"示例,帮助你在HarmonyOS开发中灵活运用ArrayBuffer。


二、核心概念

2.1 什么是ArrayBuffer?

ArrayBuffer是一块内存中的原始二进制数据缓冲区。它本身不能直接读写数据,需要通过视图(TypedArray或DataView)来操作。

关键特征

特征说明
固定大小创建时确定长度,之后不可改变
原始二进制存储的是纯字节序列,不包含类型信息
需要视图必须通过TypedArray或DataView读写
高效存储连续内存,适合高性能计算和数据传输

2.2 三层架构

┌─────────────────────────────────────────────────┐
│               ArrayBuffer (原始数据)              │
│         01101001 01101001 01101001 ...           │
├────────────────────┬────────────────────────────┤
│   TypedArray视图    │       DataView视图          │
│  (Int32Array等)    │  (按字节灵活读写)            │
│  统一类型、批量操作  │  混合类型、精确控制          │
└────────────────────┴────────────────────────────┘
  • ArrayBuffer:底层二进制数据
  • TypedArray:类型统一的数组视图(如Int32Array、Float64Array)
  • DataView:灵活的数据读写视图,支持混合类型

三、ArrayBuffer基础操作

3.1 创建ArrayBuffer

// 方式一:指定字节长度创建
let buffer = new ArrayBuffer(64);  // 创建64字节的缓冲区
console.info(`缓冲区大小: ${buffer.byteLength}`);  // 输出: 64

// 方式二:通过TypedArray自动创建
let arr = new Int32Array([10, 20, 30]);
let autoBuffer = arr.buffer;  // 自动创建的12字节缓冲区
console.info(`自动缓冲区: ${autoBuffer.byteLength}`);  // 输出: 12

3.2 复制与切片

let original = new ArrayBuffer(32);

// slice(开始位置) - 从指定位置复制到末尾
let copy1 = original.slice(0);  // 完整复制
console.info(copy1.byteLength);  // 输出: 32

// slice(开始, 结束) - 复制指定范围(不包含结束位置)
let copy2 = original.slice(4, 12);
console.info(copy2.byteLength);  // 输出: 8

3.3 判断视图

let buffer = new ArrayBuffer(16);
let view = new Int32Array(buffer);

console.info(ArrayBuffer.isView(buffer));  // false
console.info(ArrayBuffer.isView(view));    // true

四、TypedArray:类型化数组视图

4.1 TypedArray类型一览

类型元素大小(字节)说明
Int8Array18位有符号整数 (-128 ~ 127)
Uint8Array18位无符号整数 (0 ~ 255)
Int16Array216位有符号整数
Uint16Array216位无符号整数
Int32Array432位有符号整数
Uint32Array432位无符号整数
Float32Array432位浮点数
Float64Array864位浮点数

4.2 创建TypedArray

// 方式一:关联已有的ArrayBuffer
let buffer = new ArrayBuffer(32);
let view1 = new Int32Array(buffer);       // 全部8个元素
let view2 = new Int32Array(buffer, 4);    // 从第4字节开始,7个元素
let view3 = new Int32Array(buffer, 4, 2); // 从第4字节开始,2个元素

// 方式二:指定元素个数
let view4 = new Int32Array(4);  // 4个元素,自动创建16字节buffer

// 方式三:从数组创建
let view5 = new Int32Array([1, 2, 3, 4]);  // 自动创建buffer

// 方式四:静态方法
let view6 = Int32Array.of(10, 20, 30, 40);    // 从参数列表创建
let view7 = Int32Array.from([5, 6, 7, 8]);    // 从可迭代对象创建

4.3 读写数据

let arr = new Int32Array([1, 2, 3]);

// 读取
console.info(arr[0]);  // 1
console.info(arr.length);  // 3
console.info(arr.BYTES_PER_ELEMENT);  // 4

// 写入
arr[0] = 100;
arr[1] = 200;
console.info(arr[0]);  // 100

4.4 常用操作

let source = new Int32Array([1, 2, 3, 4]);
let target = new Int32Array(4);
let extended = new Int32Array(6);

// set() - 复制数据
target.set(source);          // [1, 2, 3, 4]
extended.set(source, 2);     // [0, 0, 1, 2, 3, 4]

// subarray() - 创建共享同一buffer的子视图
let sub = source.subarray(1, 3);  // [2, 3](修改sub会影响source)

// slice() - 创建独立副本
let sliced = source.slice(1, 3);  // [2, 3](修改不影响source)

4.5 复合视图

一个ArrayBuffer可以被多个不同类型的TypedArray共享:

let buffer = new ArrayBuffer(24);

// 前4字节用Uint32Array操作
let header = new Uint32Array(buffer, 0, 1);   // 1个元素
// 中间16字节用Uint8Array操作
let body = new Uint8Array(buffer, 4, 16);     // 16个元素
// 最后4字节用Float32Array操作
let footer = new Float32Array(buffer, 20, 1); // 1个元素

header[0] = 256;
body[0] = 65;      // 字符 'A'
footer[0] = 3.14;

五、DataView:灵活数据视图

5.1 基本使用

DataView支持在同一段内存中读写不同类型的数值:

let buffer = new ArrayBuffer(16);
let dv = new DataView(buffer);

// 写入不同类型的数据
dv.setInt32(0, 1000);       // 第0字节写入32位整数
dv.setFloat64(4, 3.14159);  // 第4字节写入64位浮点数
dv.setUint8(12, 255);       // 第12字节写入8位无符号整数

// 读取
console.info(dv.getInt32(0));       // 1000
console.info(dv.getFloat64(4));     // 3.14159
console.info(dv.getUint8(12));      // 255
console.info(dv.byteLength);        // 16

5.2 字节序控制

计算机存储多字节数据有两种方式:

  • 大端序(Big Endian):高位字节在前(人类习惯)
  • 小端序(Little Endian):低位字节在前(x86架构)
let buffer = new ArrayBuffer(4);
let dv = new DataView(buffer);

// 大端序写入(默认)
dv.setInt32(0, 0x12345678, false);
console.info(dv.getUint8(0).toString(16));  // "12"

// 小端序写入
dv.setInt32(0, 0x12345678, true);
console.info(dv.getUint8(0).toString(16));  // "78"

六、注意事项

6.1 ArrayBuffer长度不可变

let buffer = new ArrayBuffer(16);
// ❌ 无法改变大小,只能创建新的
// buffer.byteLength = 32;  // 错误!

// ✅ 正确做法:创建新的并复制数据
let newBuffer = new ArrayBuffer(32);
let src = new Uint8Array(buffer);
let dst = new Uint8Array(newBuffer);
dst.set(src);

6.2 TypedArray共享内存

通过subarray()创建的子视图与原视图共享同一块内存

let arr = new Int32Array([10, 20, 30, 40]);
let sub = arr.subarray(1, 3);  // [20, 30]

sub[0] = 999;
console.info(arr[1]);  // 999 ← 原数组也被修改了!

如果需要独立副本,使用slice()

6.3 内存对齐

TypedArray的偏移量必须是元素大小的整数倍:

let buffer = new ArrayBuffer(16);

// ✅ Int32Array元素4字节,偏移必须是4的倍数
let ok = new Int32Array(buffer, 4, 2);   // 偏移4 ✓

// ❌ 偏移3不是4的倍数,会报错
// let bad = new Int32Array(buffer, 3, 2);  // RangeError!

七、完整实战示例:二进制消息协议解析器

下面通过一个完整的示例,展示如何使用ArrayBuffer实现一个简单的二进制消息协议。

7.1 协议设计

消息格式(共20字节):
┌──────────┬──────────┬──────────────────────┬──────────┐
│ 消息头ID │ 消息长度  │      消息体数据       │ 校验码   │
│ 2字节    │ 2字节    │      12字节           │ 4字节    │
│ Uint16   │ Uint16   │      Float32×3       │ Uint32   │
└──────────┴──────────┴──────────────────────┴──────────┘

7.2 完整代码

/**
 * 二进制消息协议解析器
 * 演示ArrayBuffer + DataView的实际运用
 */

/** 消息数据结构 */
class MessageData {
  id: number = 0;
  length: number = 0;
  x: number = 0;
  y: number = 0;
  z: number = 0;
  checksum: number = 0;
}

/** 消息编解码器 */
class MessageCodec {
  /** 协议头大小:2(ID) + 2(Length) = 4字节 */
  static readonly HEADER_SIZE: number = 4;
  /** 消息体大小:3个Float32 = 12字节 */
  static readonly BODY_SIZE: number = 12;
  /** 校验码大小:1个Uint32 = 4字节 */
  static readonly CHECKSUM_SIZE: number = 4;
  /** 总消息大小 */
  static readonly TOTAL_SIZE: number = 20;
  /** 协议魔数 */
  static readonly MAGIC_ID: number = 0xAB;

  /**
   * 编码:将MessageData序列化为ArrayBuffer
   */
  static encode(msg: MessageData): ArrayBuffer {
    // 1. 创建固定大小的ArrayBuffer
    let buffer = new ArrayBuffer(MessageCodec.TOTAL_SIZE);
    let dv = new DataView(buffer);

    // 2. 写入消息头
    dv.setUint16(0, MessageCodec.MAGIC_ID);   // 偏移0:消息ID
    dv.setUint16(2, MessageCodec.BODY_SIZE);   // 偏移2:消息体长度

    // 3. 写入消息体(3个Float32坐标)
    dv.setFloat32(4, msg.x);                    // 偏移4:X坐标
    dv.setFloat32(8, msg.y);                    // 偏移8:Y坐标
    dv.setFloat32(12, msg.z);                   // 偏移12:Z坐标

    // 4. 计算并写入校验码
    let checksum: number = MessageCodec.computeChecksum(buffer, 0, 16);
    dv.setUint32(16, checksum);                 // 偏移16:校验码

    return buffer;
  }

  /**
   * 解码:将ArrayBuffer反序列化为MessageData
   */
  static decode(buffer: ArrayBuffer): MessageData | null {
    // 1. 校验长度
    if (buffer.byteLength < MessageCodec.TOTAL_SIZE) {
      console.error('消息长度不足');
      return null;
    }

    let dv = new DataView(buffer);

    // 2. 读取消息头
    let id: number = dv.getUint16(0);
    if (id !== MessageCodec.MAGIC_ID) {
      console.error('消息ID不匹配');
      return null;
    }

    // 3. 校验数据完整性
    let expectedChecksum: number = dv.getUint32(16);
    let actualChecksum: number = MessageCodec.computeChecksum(buffer, 0, 16);
    if (expectedChecksum !== actualChecksum) {
      console.error('校验码不匹配,数据可能损坏');
      return null;
    }

    // 4. 读取消息体
    let msg = new MessageData();
    msg.id = id;
    msg.length = dv.getUint16(2);
    msg.x = dv.getFloat32(4);
    msg.y = dv.getFloat32(8);
    msg.z = dv.getFloat32(12);
    msg.checksum = expectedChecksum;

    return msg;
  }

  /**
   * 计算校验码(简单异或校验)
   */
  static computeChecksum(buffer: ArrayBuffer, offset: number, length: number): number {
    let bytes = new Uint8Array(buffer, offset, length);
    let checksum: number = 0;
    for (let i = 0; i < bytes.length; i++) {
      checksum = (checksum + bytes[i]) & 0xFFFFFFFF;
    }
    return checksum;
  }
}

@Entry
@Component
struct BinaryProtocolDemo {
  @State encodeResult: string = '';
  @State decodeResult: string = '';
  @State hexDump: string = '';
  @State testX: string = '1.5';
  @State testY: string = '2.7';
  @State testZ: string = '3.9';

  build() {
    Column({ space: 16 }) {
      Text('📦 二进制消息协议解析器')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 60, bottom: 20 })

      // 输入区
      Column({ space: 12 }) {
        Text('输入三维坐标数据')
          .fontSize(14)
          .fontColor('#666')

        Row({ space: 12 }) {
          this.CoordInput('X', this.testX, (v: string) => { this.testX = v; })
          this.CoordInput('Y', this.testY, (v: string) => { this.testY = v; })
          this.CoordInput('Z', this.testZ, (v: string) => { this.testZ = v; })
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)

        Button('编码并解码')
          .fontSize(16)
          .backgroundColor('#1976D2')
          .fontColor(Color.White)
          .borderRadius(8)
          .onClick(() => {
            this.runEncodeDecode();
          })
          .margin({ top: 12 })
      }
      .width('90%')
      .padding(16)
      .backgroundColor('#F5F5F5')
      .borderRadius(12)

      // 编码结果
      Column() {
        Text('📤 编码结果(Hex Dump)')
          .fontSize(14)
          .fontColor('#1976D2')
          .margin({ bottom: 8 })
        Text(this.hexDump || '等待编码...')
          .fontSize(12)
          .fontColor('#333')
          .fontFamily('monospace')
      }
      .width('90%')
      .padding(12)
      .backgroundColor('#E3F2FD')
      .borderRadius(8)

      // 解码结果
      Column() {
        Text('📥 解码结果')
          .fontSize(14)
          .fontColor('#388E3C')
          .margin({ bottom: 8 })
        Text(this.decodeResult || '等待解码...')
          .fontSize(14)
          .fontColor('#333')
      }
      .width('90%')
      .padding(12)
      .backgroundColor('#E8F5E9')
      .borderRadius(8)
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  CoordInput(label: string, value: string, onChange: (v: string) => void) {
    Column({ space: 4 }) {
      Text(label)
        .fontSize(12)
        .fontColor('#999')
      TextInput({ text: value })
        .width(80)
        .height(36)
        .fontSize(14)
        .textAlign(TextAlign.Center)
        .onChange(onChange)
    }
  }

  /** 执行编码和解码流程 */
  runEncodeDecode() {
    // 1. 构建消息数据
    let msg = new MessageData();
    msg.x = parseFloat(this.testX) || 0;
    msg.y = parseFloat(this.testY) || 0;
    msg.z = parseFloat(this.testZ) || 0;

    // 2. 编码为ArrayBuffer
    let buffer = MessageCodec.encode(msg);

    // 3. Hex Dump展示
    let bytes = new Uint8Array(buffer);
    let hexParts: string[] = [];
    for (let i = 0; i < bytes.length; i++) {
      let hex: string = bytes[i].toString(16);
      hexParts.push(hex.length === 1 ? '0' + hex : hex);
    }
    this.hexDump = hexParts.join(' ').toUpperCase();

    // 4. 解码验证
    let decoded = MessageCodec.decode(buffer);
    if (decoded !== null) {
      this.decodeResult = `✓ 解码成功\n` +
        `消息ID: 0x${decoded.id.toString(16).toUpperCase()}\n` +
        `数据长度: ${decoded.length} 字节\n` +
        `坐标: (${decoded.x.toFixed(2)}, ${decoded.y.toFixed(2)}, ${decoded.z.toFixed(2)})\n` +
        `校验码: ${decoded.checksum}`;
    } else {
      this.decodeResult = '✗ 解码失败';
    }
  }
}

7.3 关键技术点解析

步骤使用的API说明
创建缓冲区new ArrayBuffer(20)分配20字节连续内存
写入头部DataView.setUint16()大端序写入16位无符号整数
写入坐标DataView.setFloat32()写入32位浮点数
校验计算Uint8Array 遍历逐字节累加校验
读取验证DataView.getXxx()按偏移量读取各类型数据

八、ArrayBuffer在HarmonyOS中的典型应用场景

场景说明
网络通信HTTP请求体、WebSocket二进制消息
文件操作读取二进制文件(图片、音频)
图像处理像素数据操作(RGBA)
加密解密密钥和密文的存储传输
传感器数据加速度计、陀螺仪的原始数据
音频处理PCM音频采样数据

九、总结

ArrayBuffer核心要点速查

要点说明
固定大小创建后不可改变长度
需要视图通过TypedArray或DataView操作
TypedArray统一类型,批量操作,数组式API
DataView混合类型,精确偏移,灵活读写
字节序DataView支持大端/小端控制
共享内存subarray()创建的视图共享底层buffer
内存对齐TypedArray偏移量必须是元素大小的倍数

选型建议

需求推荐方案
同类型批量数据TypedArray(如Int32Array)
混合类型数据结构DataView
二进制数据传输ArrayBuffer + Uint8Array
需要修改大小使用Array替代,手动管理

参考文档华为官方文档 - ArrayBuffer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值