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类型一览
| 类型 | 元素大小(字节) | 说明 |
|---|---|---|
| Int8Array | 1 | 8位有符号整数 (-128 ~ 127) |
| Uint8Array | 1 | 8位无符号整数 (0 ~ 255) |
| Int16Array | 2 | 16位有符号整数 |
| Uint16Array | 2 | 16位无符号整数 |
| Int32Array | 4 | 32位有符号整数 |
| Uint32Array | 4 | 32位无符号整数 |
| Float32Array | 4 | 32位浮点数 |
| Float64Array | 8 | 64位浮点数 |
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

352

被折叠的 条评论
为什么被折叠?



