JavaScript JavaScript 类型化数组

原创
admin 4个月前 (08-19) 阅读数 20 #JavaScript

JavaScript 类型化数组

JavaScript 类型化数组是一种特殊的数据结构,它允许开发者直接操作内存中的原始二进制数据。这种数据结构类似于数组,但提供了更高效的二进制数据处理能力,特别适用于处理大量数据或与底层系统交互的场景。

类型化数组并非为了取代 JavaScript 中的普通数组,而是为开发者提供了一种操作二进制数据的接口。在处理音频视频编辑、访问 WebSocket 原始数据等与平台相关的特性时,类型化数组显得尤为有用。类型化数组中的每个元素都是以特定格式表示的原始二进制值,支持从 8 位整数到 64 位浮点数的多种二进制格式。

类型化数组拥有许多与普通数组相似的方法和语义,但它们并不完全相同。在类型化数组上调用 Array.isArray() 会返回 false,而且并非所有普通数组的方法都适用于类型化数组,如 push 和 pop 方法。

为了实现最大的灵活性和效率,JavaScript 将类型化数组的实现分为两个核心部分:缓冲和视图。缓冲是一种表示数据块的对象,它本身没有格式,也不提供访问其内容的机制。要访问缓冲中的内存,需要使用视图。视图提供了上下文,包括数据类型、起始偏移量和元素数量。

缓冲

JavaScript 中主要有两种类型的缓冲:ArrayBuffer 和 SharedArrayBuffer。它们都是内存块的低级表示,虽然名称中包含"array",但与普通数组关系不大,因为不能直接读写它们。缓冲是通用的对象,只包含原始数据,需要通过视图来访问其内存。

缓冲支持以下操作:

  • 分配:创建新缓冲时,会分配新的内存块并初始化为 0。
  • 复制:使用 slice() 方法可以高效复制缓冲的一部分数据,无需手动复制每个字节。
  • 转移:使用 transfer() 和 transferToFixedLength() 方法可以将内存块所有权转移给新缓冲对象,适用于在不同执行上下文间转移数据而不复制的情况。转移后,原始缓冲将不再可用。SharedArrayBuffer 不能被转移。
  • 调整大小:使用 resize() 方法可以调整内存块大小,只要不超过预设的 maxByteLength 限制。SharedArrayBuffer 只能增长,不能缩小。

ArrayBuffer 和 SharedArrayBuffer 的主要区别在于所有权。ArrayBuffer 在同一时刻只能属于单个执行上下文,传递给其他上下文时会转移所有权,确保只有一个上下文能访问内存。而 SharedArrayBuffer 传递给其他上下文时不会被转移,允许多个上下文同时访问,这时可能需要使用 Atomics 方法处理竞争条件。

视图

视图是访问缓冲数据的接口,主要有两种类型:类型化数组视图和 DataView。类型化数组提供了实用方法,便于转换二进制数据;DataView 则更底层,允许精确控制数据访问方式。这两种视图的读写数据方式有很大不同。

两种视图都会使 ArrayBuffer.isView() 返回 true,并共享以下属性:

buffer
视图所引用的底层缓冲。
byteOffset
视图相对于缓冲起始位置的偏移量(以字节为单位)。
byteLength
视图的长度(以字节为单位)。

两种视图的构造函数都接受上述三个参数,但类型化数组构造函数还接受 length 参数作为元素数量而非字节长度。

类型化数组视图

类型化数组视图有自描述的名称,提供了各种常见数值类型的视图,如 Int8、Uint32 和 Float64 等。其中 Uint8ClampedArray 是一种特殊类型,它会将值钳制到 0-255 之间,在 Canvas 数据处理等场景中特别有用。

以下是主要的类型化数组类型及其特性:

类型 值范围 字节数 描述
Int8Array -128~127 1 8 位有符号整数(补码)
Uint8Array 0~255 1 8 位无符号整数
Uint8ClampedArray 0~255 1 8 位无符号整数(值会被裁剪)
Int16Array -32768~32767 2 16 位有符号整数(补码)
Uint16Array 0~65535 2 16 位无符号整数
Int32Array -2147483648~2147483647 4 32 位有符号整数(补码)
Uint32Array 0~4294967295 4 32 位无符号整数
Float32Array -3.4E38~3.4E38 4 32 位 IEEE 浮点数
Float64Array -1.8E308~1.8E308 8 64 位 IEEE 浮点数
BigInt64Array -2^63~2^63-1 8 64 位有符号整数(补码)
BigUint64Array 0~2^64-1 8 64 位无符号整数

所有类型化数组类型都由 TypedArray 类定义相同的方法和属性,唯一区别在于底层数据类型和字节数。类型化数组原则上固定长度,不支持改变长度的方法如 pop、push、shift 等。但它们提供了额外的 set 和 subarray 方法,用于优化"同缓冲多视图"场景。

DataView

DataView 是一种底层接口,提供操作缓冲中任意数据的 getter/setter API。这对于操作不同类型数据的场景特别有用,因为它可以控制字节序。默认使用大端字节序,但可以改为小端字节序。DataView 不需要对齐,多字节读写可以从任意偏移量开始。

使用类型化数组的 Web API

许多 Web API 使用类型化数组来处理二进制数据:

  • FileReader.prototype.readAsArrayBuffer():读取 Blob 或 File 的内容
  • XMLHttpRequest.prototype.send():支持使用类型化数组和 ArrayBuffer 作为参数
  • ImageData.data:是一个 Uint8ClampedArray,描述按 RGBA 序列排列的颜色数据

示例

使用视图和缓冲

创建一个 16 字节固定长度的缓冲:

const buffer = new ArrayBuffer(16);

创建一个视图,将缓冲内的数据视为 32 位有符号整数数组:

const int32View = new Int32Array(buffer);

像普通数组一样访问元素:

for (let i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

相同数据上的多个视图

可以在同一数据上创建多个视图:

const int16View = new Int16Array(buffer);

for (let i = 0; i < int16View.length; i++) {
  console.log(`索引 ${i}:${int16View[i]}`);
}

修改一个视图会影响共享同一缓冲的其他视图:

int16View[0] = 32;
console.log(`32 位数组的索引 0 处数据是:${int32View[0]}`);

从缓冲读取文本

使用 TextDecoder API 读取 UTF-8 文本:

const buffer = new ArrayBuffer(8);
const uint8 = new Uint8Array(buffer);
uint8.set([228, 189, 160, 229, 165, 189]);
const text = new TextDecoder().decode(uint8);
console.log(text); // "你好"

使用 String.fromCharCode() 读取 UTF-16 文本:

const buffer = new ArrayBuffer(8);
const uint16 = new Uint16Array(buffer);
uint16.set([0x4f60, 0x597d]);
const text = String.fromCharCode(...uint16);
console.log(text); // "你好"

使用复杂的数据结构

通过组合缓冲和不同类型视图,可以操作复杂数据结构。例如,处理类似 C 语言结构体的数据:

struct someStruct {
  unsigned long id;
  char username[16];
  float amountDue;
};

在 JavaScript 中可以这样访问:

const buffer = new ArrayBuffer(24);
const idView = new Uint32Array(buffer, 0, 1);
const usernameView = new Uint8Array(buffer, 4, 16);
const amountDueView = new Float32Array(buffer, 20, 1);

转换为普通数组

有时需要将类型化数组转换为普通数组,可以使用 Array.from() 或展开语法:

const typedArray = new Uint8Array([1, 2, 3, 4]);
const normalArray = Array.from(typedArray);
// 或
const normalArray = [...typedArray];

参见

  • 使用类型化数组实现快速的 Canvas 像素操作
  • 类型化数组:浏览器中的二进制数据
  • 字节序
  • ArrayBuffer
  • DataView
  • TypedArray
  • SharedArrayBuffer
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权本站发表,未经许可,不得转载。

作者文章
热门
最新文章
标签列表