JS `TypedArray` 与 `ArrayBuffer`:二进制数据操作与内存视图

各位观众,晚上好!我是你们今晚的二进制数据向导。准备好一起探索 JavaScript 中神秘又强大的 TypedArrayArrayBuffer 世界了吗?系好安全带,我们马上出发!

第一章:ArrayBuffer – 内存的原始画布

首先,我们来认识一下 ArrayBuffer。这家伙就像一块未经雕琢的内存画布,它代表了一段固定长度的连续内存空间。你可以把它想象成一块巨大的巧克力,你可以随意切割成小块,但巧克力的总体积是固定的。

// 创建一个 16 字节的 ArrayBuffer
const buffer = new ArrayBuffer(16);

console.log(buffer.byteLength); // 输出: 16

ArrayBuffer 自身并不能直接操作数据。它仅仅是负责分配内存。你需要用其他的“工具”来读写这段内存。这些“工具”就是我们接下来要讲的 TypedArray

第二章:TypedArray – 内存的灵活画笔

TypedArray 才是真正用来操作 ArrayBuffer 中数据的利器。它提供了一种类型化的视图,让你能够以特定的数据类型(比如整数、浮点数)来访问和修改 ArrayBuffer 中的内容。

TypedArray 有很多种类型,每种类型都对应着不同的数据格式:

TypedArray 类型 描述 每个元素占用字节数
Int8Array 8 位有符号整数 1
Uint8Array 8 位无符号整数 1
Uint8ClampedArray 8 位无符号整数,溢出时进行截断 1
Int16Array 16 位有符号整数 2
Uint16Array 16 位无符号整数 2
Int32Array 32 位有符号整数 4
Uint32Array 32 位无符号整数 4
Float32Array 32 位浮点数 4
Float64Array 64 位浮点数 8
BigInt64Array 64 位有符号整数 (ES2020) 8
BigUint64Array 64 位无符号整数 (ES2020) 8

你可以根据需要选择合适的 TypedArray 类型。例如,如果你想存储一组 32 位的整数,就可以使用 Int32Array

// 创建一个 ArrayBuffer
const buffer = new ArrayBuffer(12); // 12 字节

// 创建一个 Int32Array 视图,指向 ArrayBuffer
const int32View = new Int32Array(buffer);

// 现在我们可以通过 int32View 来操作 ArrayBuffer 中的数据了
int32View[0] = 10; // 写入第一个 32 位整数
int32View[1] = 20; // 写入第二个 32 位整数
int32View[2] = 30; // 写入第三个 32 位整数

console.log(int32View[0]); // 输出: 10
console.log(int32View[1]); // 输出: 20
console.log(int32View[2]); // 输出: 30

console.log(int32View.length); // 输出: 3 (因为 12 字节 / 4 字节/整数 = 3 个整数)

在这个例子中,我们创建了一个 12 字节的 ArrayBuffer,然后创建了一个 Int32Array 视图指向它。由于每个 Int32 占用 4 个字节,所以这个 Int32Array 视图可以容纳 3 个整数。

第三章:TypedArray 的构造函数 – 多种姿势初始化

TypedArray 的构造函数非常灵活,它支持多种初始化方式:

  1. 直接指定长度:

    // 创建一个包含 5 个元素的 Float64Array
    const float64Array = new Float64Array(5);
    
    console.log(float64Array.length); // 输出: 5
    console.log(float64Array[0]); // 输出: 0 (默认值)

    这种方式会创建一个指定长度的 TypedArray,所有元素都会被初始化为 0。

  2. 传入一个 ArrayBuffer

    const buffer = new ArrayBuffer(24); // 24 字节
    
    // 创建一个 Float64Array 视图,指向整个 ArrayBuffer
    const float64Array = new Float64Array(buffer);
    
    console.log(float64Array.length); // 输出: 3 (24 字节 / 8 字节/浮点数 = 3 个浮点数)

    这种方式会创建一个 TypedArray 视图,指向指定的 ArrayBufferTypedArray 的长度会根据 ArrayBuffer 的长度和 TypedArray 的类型自动计算。

  3. 传入一个 ArrayBuffer 和偏移量和长度:

    const buffer = new ArrayBuffer(24);
    
    // 创建一个 Float64Array 视图,从 ArrayBuffer 的第 8 字节开始,长度为 2
    const float64Array = new Float64Array(buffer, 8, 2);
    
    console.log(float64Array.length); // 输出: 2
    console.log(float64Array.byteOffset); // 输出: 8 (视图从 ArrayBuffer 的第 8 字节开始)
    console.log(float64Array.byteLength); // 输出: 16 (视图总共占用 16 字节)

    这种方式可以让你创建一个 TypedArray 视图,指向 ArrayBuffer 的一部分。 byteOffset 指定视图的起始位置(以字节为单位),length 指定视图的元素个数。

  4. 传入一个已有的数组或 TypedArray

    const array = [1, 2, 3, 4, 5];
    
    // 创建一个 Int32Array,内容和 array 相同
    const int32Array = new Int32Array(array);
    
    console.log(int32Array.length); // 输出: 5
    console.log(int32Array[0]); // 输出: 1
    console.log(int32Array[4]); // 输出: 5

    这种方式会创建一个新的 TypedArray,并将数组或 TypedArray 中的内容复制到新的 TypedArray 中。

第四章:DataView – 内存的瑞士军刀

DataView 是另一个可以用来操作 ArrayBuffer 中数据的工具。它比 TypedArray 更加灵活,允许你以任意的数据类型和字节顺序来读写 ArrayBuffer 中的数据。你可以把它想象成一把瑞士军刀,可以应对各种复杂的二进制数据格式。

const buffer = new ArrayBuffer(8);

// 创建一个 DataView 视图,指向 ArrayBuffer
const dataView = new DataView(buffer);

// 以 big-endian 字节顺序写入一个 32 位整数
dataView.setInt32(0, 0x12345678, false); // offset, value, littleEndian

// 以 little-endian 字节顺序读取一个 32 位整数
const value = dataView.getInt32(0, true); // offset, littleEndian

console.log(value.toString(16)); // 输出: 78563412 (little-endian 字节顺序)

DataView 提供了一系列方法来读写不同类型的数据:

  • getInt8(byteOffset) / setInt8(byteOffset, value)
  • getUint8(byteOffset) / setUint8(byteOffset, value)
  • getInt16(byteOffset, littleEndian) / setInt16(byteOffset, value, littleEndian)
  • getUint16(byteOffset, littleEndian) / setUint16(byteOffset, value, littleEndian)
  • getInt32(byteOffset, littleEndian) / setInt32(byteOffset, value, littleEndian)
  • getUint32(byteOffset, littleEndian) / setUint32(byteOffset, value, littleEndian)
  • getFloat32(byteOffset, littleEndian) / setFloat32(byteOffset, value, littleEndian)
  • getFloat64(byteOffset, littleEndian) / setFloat64(byteOffset, value, littleEndian)
  • getBigInt64(byteOffset, littleEndian) / setBigInt64(byteOffset, value, littleEndian)
  • getBigUint64(byteOffset, littleEndian) / setBigUint64(byteOffset, value, littleEndian)

注意 littleEndian 参数,它指定了字节顺序。true 表示 little-endian,false 表示 big-endian。

第五章:字节序 (Endianness) – 数据的排列方式

字节序指的是多字节数据在内存中的存储顺序。主要有两种类型:

  • Big-endian (大端序): 最高有效字节 (MSB) 存储在最低的内存地址。
  • Little-endian (小端序): 最低有效字节 (LSB) 存储在最低的内存地址。

举个例子,假设我们要存储一个 32 位的整数 0x12345678

  • Big-endian: 内存中的存储顺序是 12 34 56 78
  • Little-endian: 内存中的存储顺序是 78 56 34 12

不同的计算机体系结构可能使用不同的字节序。例如,PowerPC 架构通常使用 big-endian,而 x86 架构通常使用 little-endian。

在使用 DataView 操作二进制数据时,需要特别注意字节序,确保数据能够正确地被读取和解析。

第六章:TypedArray vs DataView – 如何选择?

TypedArrayDataView 都可以用来操作 ArrayBuffer 中的数据,但它们各有特点:

特性 TypedArray DataView
类型限制 只能以一种类型化的视图访问数据 可以以任意类型和字节顺序访问数据
灵活性 较低 较高
性能 通常比 DataView 略好 略差
适用场景 需要以统一的数据类型高效地访问数据时 需要处理复杂的二进制数据格式,或者需要控制字节顺序时

简单来说,如果你的数据都是同一种类型,并且需要高性能,那么 TypedArray 是一个不错的选择。如果你的数据格式比较复杂,或者需要处理字节序问题,那么 DataView 更加适合。

第七章:实际应用 – 图像处理、音频处理、网络传输

TypedArrayArrayBuffer 在很多领域都有广泛的应用:

  • 图像处理: 可以用 TypedArray 来存储图像的像素数据,进行图像的缩放、旋转、滤镜等操作。
  • 音频处理: 可以用 TypedArray 来存储音频的采样数据,进行音频的编码、解码、混音等操作。
  • WebGL: TypedArray 是 WebGL 中用于传递顶点数据、纹理数据等的重要数据结构。
  • 网络传输: 可以用 ArrayBuffer 来构建二进制协议,进行高效的网络数据传输。
  • 文件操作: 可以用 ArrayBuffer 读取和写入二进制文件。

第八章:代码示例 – 图像像素操作

让我们来看一个简单的图像像素操作的例子。假设我们有一个包含图像像素数据的 Uint8ClampedArray,我们可以通过修改数组中的值来改变图像的颜色。

// 假设 imageData 是一个 ImageData 对象,包含图像的像素数据
// imageData.data 是一个 Uint8ClampedArray,存储着 RGBA 像素数据

// 将图像的红色通道全部设置为 255
for (let i = 0; i < imageData.data.length; i += 4) {
  imageData.data[i] = 255; // 红色通道
}

// 将修改后的像素数据绘制到 canvas 上
context.putImageData(imageData, 0, 0);

在这个例子中,我们遍历 Uint8ClampedArray,每次跳过 4 个元素(因为每个像素包含 4 个通道:红色、绿色、蓝色、Alpha)。我们将每个像素的红色通道设置为 255,这样图像就会变成红色。

第九章:Uint8ClampedArray – 颜色的守护者

Uint8ClampedArray 是一种特殊的 TypedArray,它专门用于存储颜色数据。它的特点是,当写入的值超出 0-255 的范围时,会自动进行截断:

  • 小于 0 的值会被截断为 0
  • 大于 255 的值会被截断为 255

这可以防止颜色值溢出,保证颜色的正确性。

const clampedArray = new Uint8ClampedArray(4);

clampedArray[0] = -10; // 截断为 0
clampedArray[1] = 300; // 截断为 255
clampedArray[2] = 100;
clampedArray[3] = 200;

console.log(clampedArray[0]); // 输出: 0
console.log(clampedArray[1]); // 输出: 255
console.log(clampedArray[2]); // 输出: 100
console.log(clampedArray[3]); // 输出: 200

第十章:总结 – 二进制数据的力量

ArrayBufferTypedArray 是 JavaScript 中处理二进制数据的强大工具。它们可以让你直接操作内存,实现高性能的数据处理。DataView 则提供了更加灵活的数据访问方式,可以处理各种复杂的二进制数据格式。

掌握了这些工具,你就可以在 JavaScript 中进行图像处理、音频处理、网络传输等各种高级操作,打开二进制数据世界的大门!

好了,今天的讲座就到这里。希望大家有所收获!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注