前端二进制处理方法

66 阅读7分钟

ArrayBuffer

ArrayBuffer 对象代表存储二进制数据的一段内存,是一个字节数组。

它不能被直接读写,需要创建视图(TypedArray, DataView)来对它进行操作,视图可以以指定格式操作二进制数据。

TypedArray

TypedArray 并不直接存储数据,它只是一个视图,建立在ArrayBuffer 之上。

是一个父类或者说一个家族。它的成员是像 Int8ArrayUint8ArrayFloat32Array 等具体的类型化数组构造函数。

Int8Array8 位有符号整数1 字节-128 到 127
Uint8Array8 位无符号整数1 字节0 到 255
Int16Array16 位有符号整数2 字节-32768 到 32767
Uint16Array16 位无符号整数2 字节0 到 65535
Int32Array32 位有符号整数4 字节-2,147,483,648 到 2,147,483,647
Uint32Array32 位无符号整数4 字节0 到 4,294,967,295
Float32Array32 位浮点数 (单精度)4 字节约 ±3.4e38
Float64Array64 位浮点数 (双精度)8 字节约 ±1.8e308
BigInt64Array64 位有符号大整数 (ES2020)8 字节任意大的整数,需要使用 BigInt 类型
BigUint64Array64 位无符号大整数 (ES2020)8 字节任意大的正整数,需要使用 BigInt 类型
Uint8ClampedArray8 位无符号整数,溢出时会被钳制 (常用于Canvas)1 字节0 到 255,溢出时会自动调整到 0 或 255

怎么用?

const buffer = new ArrayBuffer(16); // 创建一个16字节的ArrayBuffer
const uint8 = new Uint8Array(buffer); // 创建一个 Uint8Array 视图
const int32 = new Int32Array(buffer); // 创建一个 Int32Array 视图 

console.log(uint8.length); // 16 (16个8位无符号整数)
console.log(int32.length); // 4 (4个32位有符号整数)

//写入
uint8[0] = 255;
//读取
console.log(uint8[0]); // 255

int32[0] = 123456789;
console.log(int32[0]); // 123456789

 Uint8ClampedArray是什么:

如果写入的数值小于 0,它会被钳制为 0。 如果写入的数值大于 255,它会被钳制为 255。

这种钳制行为在处理图像像素数据时非常有用。图像的每个颜色通道(红、绿、蓝、透明度)通常用一个 0 到 255 的整数来表示。

当你在进行图像处理(如滤镜、亮度调整、对比度增强等)时,计算结果可能会超出 0-255 的范围。

如果使用普通的 Uint8Array,超出范围的值会环绕,导致颜色出现意想不到的扭曲(比如 256 变成了 0,257 变成了 1,这在颜色上看起来是突变的)。

typedArray的set方法

用于将一个另一个数组中的多个元素一次性复制到当前 Typed Array 的指定 offset 位置。

let array1 = new Float32Array([1, 2, 3]);
let array2 = new Float32Array(5);  // A new array with more space

array2.set(array1);  // Copies [1, 2, 3] into array2, starting at index 0
console.log(array2); // Output: Float32Array [1, 2, 3, 0, 0]

array2.set([4, 5], 3);  // Starts at index 3 and adds [4, 5]
console.log(array2); // Output: Float32Array [1, 2, 3, 4, 5]

DataView

相比 TypedArray,DataView 更加灵活。它允许你在同一 ArrayBuffer 中,以任意字节偏移量和任意字节序(big-endian或little-endian)读取或写入不同类型的数据。

注:大端序(Big-Endian): 将数据的最高有效字节存放在最低的内存地址。小端序(Little-Endian): 将数据的最低有效字节存放在最低的内存地址。计算机内部,小端序被广泛应用于现代 CPU 内部存储数据;而在其他场景,比如网络传输和文件存储则使用大端序

Dataview的读取方法和写入方法:

//读取方法:这里byteOffset必须写。第二个参数可选,为布尔值。true: 小端序。false (或省略): 大端序。
getInt8(byteOffset): 读取一个有符号8位整数。
getUint8(byteOffset): 读取一个无符号8位整数。
getInt16(byteOffset, littleEndian): 读取一个有符号16位整数。
getUint16(byteOffset, littleEndian): 读取一个无符号16位整数。
getInt32(byteOffset, littleEndian): 读取一个有符号32位整数。
getUint32(byteOffset, littleEndian): 读取一个无符号32位整数。
getFloat32(byteOffset, littleEndian): 读取一个32位浮点数。
getFloat64(byteOffset, littleEndian): 读取一个64位浮点数。
//写入方法:byteOffset是开始写入字节的起始偏移量。第三个参数可选,同上
setInt8(byteOffset, value): 写入一个有符号8位整数。
setUint8(byteOffset, value): 写入一个无符号8位整数。
setInt16(byteOffset, value, littleEndian): 写入一个有符号16位整数。
setUint16(byteOffset, value, littleEndian): 写入一个无符号16位整数。
setInt32(byteOffset, value, littleEndian): 写入一个有符号32位整数。
setUint32(byteOffset, value, littleEndian): 写入一个无符号32位整数。
setFloat32(byteOffset, value, littleEndian): 写入一个32位浮点数。
setFloat64(byteOffset, value, littleEndian): 写入一个64位浮点数。

示例;

// 1. 创建一个 ArrayBuffer
const buffer = new ArrayBuffer(16); // 创建一个16字节的缓冲区

//ArrayBuffer: [ B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | B13 | B14 | B15 ]
//Byte Index:    0    1    2    3    4    5    6    7    8     9    10    11    12   13   14   15

// 2. 创建一个 DataView 来操作这个 ArrayBuffer
const dataView = new DataView(buffer);

// 3. 写入数据
// 在偏移量 0 处写入一个 8 位无符号整数 (0-255)
dataView.setUint8(0, 255); // 0xFF

// 在偏移量 1 处(索引为1)写入一个 16 位有符号整数
// 默认是大端序 (false 或不传)
dataView.setInt16(1, -12345); // -12345 (0xCFC7)

// 在偏移量 3 处写入一个 32 位浮点数 (使用小端序)
dataView.setFloat32(3, 3.14159, true); // true 表示小端序

// 在偏移量 7 处写入一个 64 位浮点数 (使用大端序)
dataView.setFloat64(7, 123456789.1234567, false); // false 表示大端序 (或不传)

console.log("--- 写入数据完成 ---");

// 4. 读取数据
console.log("Uint8 at offset 0:", dataView.getUint8(0)); // 255

// 读取在偏移量 1 处的 16 位有符号整数 (默认大端序)
console.log("Int16 at offset 1 (Big-Endian):", dataView.getInt16(1)); // -12345

// 读取在偏移量 3 处的 32 位浮点数 (小端序)
console.log("Float32 at offset 3 (Little-Endian):", dataView.getFloat32(3, true)); // 3.141590118408203

// 读取在偏移量 7 处的 64 位浮点数 (大端序)
console.log("Float64 at offset 7 (Big-Endian):", dataView.getFloat64(7, false)); // 123456789.1234567


两者区别

特性Typed ArraysDataView
用途处理同构的二进制数据块处理异构的二进制数据,需要精细控制字节顺序和偏移量
数据类型每个视图固定一种数据类型可以在同一个视图中读写不同类型的数据
访问方式数组索引 ([]),方便迭代和批量操作方法调用 (get*()set*())
字节顺序平台默认字节顺序(无法直接控制)可以明确指定大端序或小端序
性能通常更优,因为类型已知,可高度优化略低于 Typed Arrays,因为每次访问都需要类型检查和偏移量计算
解释数据同构 (Homogeneous) :所有元素都视为同一种类型。例如,Uint8Array 认为所有字节都是无符号 8 位整数。异构 (Heterogeneous) :可以读取/写入不同类型的数据到 ArrayBuffer 的任何指定字节偏移量。
元素类型固定:一旦创建,其元素类型就确定了(如 Int32Array 只能存取 32 位整数)。灵活:通过不同的方法(getInt8getFloat32 等)在运行时指定要读写的类型。
访问方式数组索引:通过 array[index] 的方式访问元素,类似常规数组。每个 index 代表一个类型化数据单元。偏移量:通过 dataView.get*(byteOffset, littleEndian) 或 dataView.set*(byteOffset, value, littleEndian) 的方式,精确到字节偏移量。
读写粒度数据类型单位:每次读写操作都是以其类型的大小为单位(如 Int32Array 读写 4 字节)。字节单位:每次读写操作都从指定的字节偏移量开始,并根据方法名(如 getFloat64)读取相应的字节数。
创建方式new TypeArray(buffer) new DataView(buffer, [byteOffset], [byteLength])
适用场景高效处理大量相同类型数据- 对性能要求较高(typed array比dataview性能高一些)。从一个二进制缓冲区中读取或写入不同类型的数据时