为什么需要二进制数据处理
JavaScript 传统上不擅长处理二进制数据。但现代 Web 应用经常需要处理图片、音视频、网络协议等二进制数据。ArrayBuffer、DataView、TypedArray 提供了高效的二进制数据处理能力。
一、ArrayBuffer
1.1 创建 ArrayBuffer
// 创建指定字节数的缓冲区
const buffer = new ArrayBuffer(8); // 8 字节
console.log(buffer.byteLength); // 8
// 不能直接读写,需要通过视图
1.2 ArrayBuffer 不可变性
ArrayBuffer 创建后大小固定,不能调整。如果需要调整,需要创建新的缓冲区:
const buffer = new ArrayBuffer(8);
const resized = new ArrayBuffer(16);
new Uint8Array(resized).set(new Uint8Array(buffer));
二、TypedArray
2.1 TypedArray 类型
| 类型 | 大小 | 说明 |
|---|---|---|
| Int8Array | 1 字节 | 有符号字节 |
| Uint8Array | 1 字节 | 无符号字节 |
| Uint8ClampedArray | 1 字节 | 截断而非溢出 |
| Int16Array | 2 字节 | 有符号短整型 |
| Uint16Array | 2 字节 | 无符号短整型 |
| Int32Array | 4 字节 | 有符号整型 |
| Uint32Array | 4 字节 | 无符号整型 |
| Float32Array | 4 字节 | 单精度浮点 |
| Float64Array | 8 字节 | 双精度浮点 |
2.2 创建 TypedArray
// 从 ArrayBuffer
const buffer = new ArrayBuffer(8);
const int32View = new Int32Array(buffer);
// 从普通数组
const int32Array = new Int32Array([1, 2, 3, 4]);
// 从长度
const zeros = new Int32Array(4); // [0, 0, 0, 0]
2.3 字节偏移与长度
const buffer = new ArrayBuffer(16);
const view1 = new Int32Array(buffer);
const view2 = new Int32Array(buffer, 8, 1); // 从 byte offset 8 开始,长度 1
三、DataView
3.1 为什么需要 DataView
TypedArray 对字节序(endianness)的处理取决于系统,可能导致跨平台兼容性问题。DataView 允许显式指定字节序:
const buffer = new ArrayBuffer(8);
const dataView = new DataView(buffer);
// 大端序写入
dataView.setInt32(0, 0x12345678, false); // 大端序
// 小端序读取
console.log(dataView.getInt32(0, true)); // 小端序读取
3.2 DataView 方法
// 写入
dataView.setInt8(byteOffset, value);
dataView.setUint8(byteOffset, value);
dataView.setInt16(byteOffset, value, littleEndian);
dataView.setInt32(byteOffset, value, littleEndian);
dataView.setFloat32(byteOffset, value, littleEndian);
dataView.setFloat64(byteOffset, value, littleEndian);
// 读取
dataView.getInt8(byteOffset);
dataView.getUint8(byteOffset);
dataView.getInt16(byteOffset, littleEndian);
dataView.getInt32(byteOffset, littleEndian);
dataView.getFloat32(byteOffset, littleEndian);
dataView.getFloat64(byteOffset, littleEndian);
四、字节序
4.1 大端序 vs 小端序
- 大端序(Big Endian):高位字节在前
- 小端序(Little Endian):低位字节在前
值 0x12345678 在内存中:
大端序:12 34 56 78
小端序:78 56 34 12
4.2 实际场景
网络协议通常用大端序,x86 处理器用小端序:
// 解析 IPv4 地址
const buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([192, 168, 1, 1]);
// 大端序读取(网络字节序)
const view = new DataView(buffer);
const ip = `${view.getUint8(0)}.${view.getUint8(1)}.${view.getUint8(2)}.${view.getUint8(3)}`;
console.log(ip); // '192.168.1.1'
五、实际应用
5.1 读取文件二进制
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const buffer = await file.arrayBuffer();
const view = new DataView(buffer);
// 读取文件头
const magic = view.getUint32(0, false);
console.log('Magic number:', magic.toString(16));
});
5.2 结构化二进制数据
// 固定长度字符串(16 字节)
const NAME_SIZE = 16;
function writePerson(buffer, offset, name, age) {
const view = new DataView(buffer);
const nameBytes = new TextEncoder().encode(name.padEnd(NAME_SIZE, '\0'));
new Uint8Array(buffer, offset, NAME_SIZE).set(nameBytes);
view.setUint8(offset + NAME_SIZE, age);
}
function readPerson(buffer, offset) {
const view = new DataView(buffer);
const nameBytes = new Uint8Array(buffer, offset, NAME_SIZE);
const name = new TextDecoder().decode(nameBytes).trimEnd('\0');
const age = view.getUint8(offset + NAME_SIZE);
return { name, age };
}
六、面试高频考点
考点 1:ArrayBuffer vs TypedArray
ArrayBuffer 是原始二进制数据容器,不能直接读写。TypedArray 是视图,提供类型化的读写能力。
考点 2:DataView vs TypedArray
DataView 可以显式指定字节序,TypedArray 的字节序取决于系统。
考点 3:字节序处理
大端序高位字节在前,小端序低位字节在前。处理跨平台数据时需要明确指定字节序。