ArrayBuffer、TypedArrays 和 DataView三兄弟是什么关系?

259 阅读6分钟

u27kmefj4wte0i06ayra.png

一、引言

在Web开发中,我们通常使用JSON格式进行数据通信,然而有时候JSON并不能满足我们的需求,我们希望可以用二进制直接处理图像、视频,或者Scoket通信。JavaScript 提供了一套强大的工具——ArrayBuffer、TypedArrays 和 DataView。这些工具可以帮助开发者更好地管理和处理原始的二进制数据。

ArrayBuffer 是一种存储原始二进制数据的固定长度内存空间,TypedArrays 是对 ArrayBuffer 的一种类型化视图,每个数组项具有相同大小和类型,而 DataView 则是 ArrayBuffer 的灵活视图,允许不同大小和类型的数据存取。三者相辅相成,共同为 JavaScript 提供了高效的二进制数据处理能力。

这篇文章旨在为Web开发者提供一份详尽的指导,通过剖析 ArrayBuffer、TypedArrays 和 DataView 的基础知识、使用场景、性能优化技巧等内容,帮助读者更好地掌握这些工具,并在实际项目中应用最佳实践。

二、ArrayBuffer 的基础知识

什么是 ArrayBuffer?

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。在其他语言中,它往往被称为“byte array”。ArrayBuffer 不能直接操作数据,而是需要通过类型化数组(TypedArray)或 DataView 进行视图操作。


// 创建一个 8 字节(64 位)的 ArrayBuffer

let buffer = new ArrayBuffer(8);

// 查看 buffer 的长度

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

创建 ArrayBuffer 的方法和语法

可以使用 ArrayBuffer 构造函数来创建一个指定字节长度的内存区域。例子:


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

ArrayBuffer 的主要属性和方法

主要属性:

  • byteLength:ArrayBuffer 的字节长度,该属性是只读的。

主要方法:

  • slice:返回一个新的 ArrayBuffer,从原始 ArrayBuffer 复制一个字节序列。

let buffer = new ArrayBuffer(8);

let newBuffer = buffer.slice(2, 6); // 复制从第 2 字节到第 6 字节的序列

三、TypedArrays 概述

什么是 TypedArrays

TypedArrays 是对 ArrayBuffer 的类型化视图,使得 JavaScript 可以以原生的方式读取和操作原始的二进制数据。常用的 TypedArrays 包括 Int8Array、Uint8Array、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array 和 Float64Array 等。


let buffer = new ArrayBuffer(16);

let int32View = new Int32Array(buffer);

int32View[0] = 42;

console.log(int32View[0]); // 输出: 42

常见的 TypedArrays 类型及其区别

不同类型的 TypedArrays 适用于不同的数据处理需求。例如:

  • Int8ArrayUint8Array 用于处理 8 位整数,常用于图像处理。

  • Int16ArrayUint16Array 用于处理 16 位整数,适用于需要存储较大范围整数的应用。

  • Float32ArrayFloat64Array 用于处理浮点数,常用于科学计算或图形处理。

不同类型的 TypedArray 支持的精度和范围如下:

  • Int8Array:精度范围:-128 到 127

  • Uint8Array:精度范围:0 到 255

  • Uint8ClampedArray:精度范围:0 到 255(值被限制在这个范围内)

  • Int16Array:精度范围:-32,768 到 32,767

  • Uint16Array:精度范围:0 到 65,535

  • Int32Array:精度范围:-2,147,483,648 到 2,147,483,647

  • Uint32Array:精度范围:0 到 4,294,967,295

  • Float32Array:精度范围:1.2e-38 到 3.4e38(单精度浮点数)

  • Float64Array:精度范围:5.0e-324 到 1.8e308(双精度浮点数)

TypedArrays 的创建及其方法和属性

可以通过传入 ArrayBuffer 来创建 TypedArray:


let buffer = new ArrayBuffer(16);

let float32View = new Float32Array(buffer);

常用的属性和方法:

  • length:返回 TypedArray 元素的数量。

  • set:将一个数组或 TypedArray 复制到当前 TypedArray。

  • subarray:返回当前 TypedArray 的一个视图。


let buffer = new ArrayBuffer(16);

let int16View = new Int16Array(buffer);

int16View.set([1, 2, 3]);

let sub = int16View.subarray(1, 3);

console.log(sub); // 输出: Int16Array(2) [2, 3]

与普通数组的对比

TypedArrays 与普通数组的区别在于:

  • TypedArrays 存储的是原始二进制数据,而普通数组存储的是 JavaScript 对象。

  • TypedArrays 的元素大小固定,并且是连续的内存块,操作性能更高。

四、DataView 的详细解析

DataView 的定义和应用

DataView 提供了对 ArrayBuffer 更灵活的操作视图,可以以任意的数值类型和字节序读取和写入数据,适用于需要处理多种类型数据的场景。


let buffer = new ArrayBuffer(16);

let view = new DataView(buffer);

view.setInt8(1, 42);

console.log(view.getInt8(1)); // 输出: 42

创建 DataView 的方法和语法

DataView 需要传入 ArrayBuffer 进行创建,可以指定开始读取的字节偏移量和长度:


let buffer = new ArrayBuffer(16);

let view = new DataView(buffer, 4, 8); // 从第 4 个字节开始,占用 8 个字节

DataView 的方法和属性详解

主要方法:

  • getInt8/setInt8:读取/写入 8 位整数。

  • getUint8/setUint8:读取/写入 8 位无符号整数。

  • getInt16/setInt16:读取/写入 16 位整数。

  • getUint16/setUint16:读取/写入 16 位无符号整数。

  • getInt32/setInt32:读取/写入 32 位整数。

  • getUint32/setUint32:读取/写入 32 位无符号整数。

  • getFloat32/setFloat32:读取/写入 32 位浮点数。

  • getFloat64/setFloat64:读取/写入 64 位浮点数。


let buffer = new ArrayBuffer(16);

let view = new DataView(buffer);

view.setFloat32(0, 3.14);

console.log(view.getFloat32(0)); // 输出: 3.140000104904175

使用 DataView 处理不同类型数据的实例

例如,我们可以通过 DataView 在同一个 ArrayBuffer 中存储不同类型的数据:


let buffer = new ArrayBuffer(16);

let view = new DataView(buffer);

view.setInt8(0, 32);

view.setFloat32(1, 3.14);

console.log(view.getInt8(0)); // 输出: 32

console.log(view.getFloat32(1)); // 输出: 3.140000104904175

五、ArrayBuffer、TypedArrays 和 DataView 之间的关系

ArrayBuffer 是一种内存区域,TypedArrays 是其固定类型的视图,而 DataView 是其灵活的视图。它们相辅相成,共同为 JavaScript 提供了高效的二进制数据处理能力。

选择正确的工具取决于具体的使用场景:

  • 需操作同类数据时,使用 TypedArrays。

  • 需操作多种不同类型数据时,使用 DataView。

注意避免以下误区:

  • 忽视字节序问题,导致数据读取错误。

  • 数据类型选择不当,造成内存浪费或性能下降。

六、实际应用与示例

图像处理

一个在线照片编辑器需要在用户上传图片后对其进行滤镜应用和调整。


// 获取上传的图像数据

let canvas = document.createElement('canvas');

let context = canvas.getContext('2d');

let img = document.getElementById('uploadedImage');

canvas.width = img.width;

canvas.height = img.height;

context.drawImage(img, 0, 0);

// 提取图像像素数据

let imageData = context.getImageData(0, 0, canvas.width, canvas.height);

let buffer = imageData.data.buffer; // ArrayBuffer

let uint8View = new Uint8ClampedArray(buffer);

// 应用灰度滤镜

for (let i = 0; i < uint8View.length; i += 4) {

let avg = (uint8View[i] + uint8View[i+1] + uint8View[i+2]) / 3;

uint8View[i] = avg; // R

uint8View[i+1] = avg; // G

uint8View[i+2] = avg; // B

}

// 更新画布

context.putImageData(imageData, 0, 0);

音频处理

一个实时音频可视化工具,需要对音频流进行频谱分析:


navigator.mediaDevices.getUserMedia({ audio: true })

.then(stream => {

let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let analyser = audioContext.createAnalyser();

let source = audioContext.createMediaStreamSource(stream);

source.connect(analyser);

analyser.fftSize = 2048;

let bufferLength = analyser.frequencyBinCount;

let dataArray = new Uint8Array(bufferLength);

function draw() {

requestAnimationFrame(draw);

analyser.getByteFrequencyData(dataArray);

// 渲染频谱数据

renderFrequencyData(dataArray);

}

draw();

});

网络协议解析

一个实时股票行情应用,通过 WebSocket 接收二进制数据包并解析


let socket = new WebSocket('wss://stock-data.example.com');

socket.binaryType = 'arraybuffer';

socket.onmessage = function(event) {

let buffer = event.data;

let dataView = new DataView(buffer);

// 假设数据包格式:[股票代码(4字节), 价格(4字节), 交易量(4字节)]

let stockCode = dataView.getUint32(0);

let price = dataView.getFloat32(4);

let volume = dataView.getUint32(8);

// 更新UI

updateStockInfo(stockCode, price, volume);

};