TypeScript中的`TypedArray`

TypeScript中的`TypedArray`

本文解释了TypeScript中的TypedArray

我们将讲解TypeScript中的TypedArray,并包含实际示例。

YouTube Video

TypeScript中的TypedArray

TypedArray是一种高效处理二进制数据的机制。它特别适用于底层二进制操作,如大型图像数据、网络字节流和WebGL的数值数组。

如何创建一个ArrayBuffer

ArrayBuffer表示一个固定长度的字节区域。首先,创建一个缓冲区,并检查其大小和字节长度。

1// Create an ArrayBuffer of 16 bytes
2const buffer: ArrayBuffer = new ArrayBuffer(16);
3console.log("buffer.byteLength:", buffer.byteLength); // 16
  • 这段代码创建了一个16字节的空区域。ArrayBuffer本身没有读写功能,因此需要通过TypedArrayDataView进行访问。

TypedArray的类型

TypedArray有多种类型,例如以下这些。它们根据处理的数据类型和大小而有所不同。

TypedArray 数据类型 位大小
Int8Array 8位整数 8位
Uint8Array 无符号8位整数 8位
Uint8ClampedArray 限制范围的无符号8位整数 8位
Int16Array 16位整数 16位
Uint16Array 无符号16位整数 16位
Int32Array 32位整数 32位
Uint32Array 无符号32位整数 32位
Float32Array 32位浮点数 32位
Float64Array 64位浮点数 64位

基本的TypedArray(如Uint8ArrayInt16ArrayFloat32Array等)

TypedArrayArrayBuffer之上创建一个“类型化视图”。以下是一些典型的TypedArray创建和使用示例。

 1// Create a buffer and different typed views over it
 2const buf = new ArrayBuffer(8); // 8 bytes
 3
 4// Create views
 5const u8 = new Uint8Array(buf);    // 8 x uint8
 6const i16 = new Int16Array(buf);   // 4 x int16 (since each int16 is 2 bytes)
 7const f32 = new Float32Array(buf); // 2 x float32 (4 bytes each)
 8
 9console.log("Uint8 length:", u8.length);
10console.log("Int16 length:", i16.length);
11console.log("Float32 length:", f32.length);
  • 通过在同一个ArrayBuffer上创建多个视图,可以用不同的类型或粒度读写同一块内存。视图的length是元素数量,byteLength是字节数。

读写操作(字节级操作)

当你向TypedArray写入值时,内存中相应的字节会被更新。通过不同的视图读取同一个缓冲区,可以看到变化。

 1// Demonstrate writing via one view and reading via another
 2const buffer2 = new ArrayBuffer(4);
 3const u8view = new Uint8Array(buffer2);
 4const u32view = new Uint32Array(buffer2);
 5
 6u8view[0] = 0x78;
 7u8view[1] = 0x56;
 8u8view[2] = 0x34;
 9u8view[3] = 0x12;
10
11console.log("Uint8 bytes:", Array.from(u8view)); // [120, 86, 52, 18]
12console.log("Uint32 value (platform endianness):", u32view[0]); // value depends on endianness
  • 在这个例子中,我们先写入一个字节序列,然后将同一区域作为32位整数读取。请注意,输出结果取决于执行环境的字节序。

字节序(Endian)与DataView

如果要控制依赖环境的字节序问题,DataView会很有用。DataView允许你在读写时指定字节序。

 1// Use DataView to read/write with explicit endianness control
 2const b = new ArrayBuffer(4);
 3const dv = new DataView(b);
 4
 5// write little-endian 32-bit integer
 6dv.setUint32(0, 0x12345678, true);
 7
 8// read as little-endian and big-endian
 9const little = dv.getUint32(0, true);
10const big = dv.getUint32(0, false);
11
12console.log("little-endian read:", little.toString(16)); // "12345678"
13console.log("big-endian read:", big.toString(16));       // different value
  • DataView是一种能够详细读写字节的便利机制。它支持多种类型,如有符号或无符号整数和浮点数,并且可以明确指定字节序(Endian),在实现二进制协议时非常有用。

subarrayslice的区别

TypedArraysubarray返回共享原始缓冲区的视图,而slice返回一个新的副本。根据使用的方式不同,性能和副作用也不同。

1const base = new Uint8Array([1, 2, 3, 4, 5]);
2
3const shared = base.subarray(1, 4); // shares underlying buffer
4const copied = base.slice(1, 4);    // copies data
5
6shared[0] = 99;
7console.log("base after shared modification:", base); // shows change
8console.log("copied remains:", copied);              // unaffected
  • 如果你修改了共享视图,原始数组也会发生变化,这可能会导致意外的副作用。如果你想安全地保留原始数组,可以提前使用 slice() 创建一个副本。

缓冲区复制与类型转换(不同TypedArray之间的转换)

我们将讲解如何在不同TypedArray之间复制数据,以及如何用set方法粘贴部分内容。

 1// Copy and convert between typed arrays
 2const src = new Float32Array([1.5, 2.5, 3.5]);
 3const dst = new Uint16Array(src.length);
 4
 5// Convert by mapping (explicit conversion)
 6for (let i = 0; i < src.length; i++) {
 7  dst[i] = Math.round(src[i]); // simple conversion rule
 8}
 9console.log("converted dst:", Array.from(dst)); // [2, 2, 4]
10
11// Using set to copy bytes (requires compatible underlying buffer or same element type)
12const src2 = new Uint8Array([10, 20, 30]);
13const dst2 = new Uint8Array(6);
14dst2.set(src2, 2); // copy src2 into dst2 starting at index 2
15console.log("dst2 after set:", Array.from(dst2)); // [0,0,10,20,30,0]
  • 如果元素类型不同,则需要进行数值转换(比如四舍五入或范围检查),而不能简单复制。set可以在同类型元素的TypedArray之间进行高速复制。

实用示例:二进制协议解析器(简单实现)

这里介绍一个简单的解析器示例,它读取由1字节类型、2字节数据长度和后续负载组成的定长二进制数据。

 1// Simple binary message parser:
 2// format: [type: uint8][length: uint16 BE][payload: length bytes]
 3function parseMessages(buffer: ArrayBuffer) {
 4  const dv = new DataView(buffer);
 5  let offset = 0;
 6  const messages: { type: number; payload: Uint8Array }[] = [];
 7
 8  while (offset + 3 <= dv.byteLength) {
 9    const type = dv.getUint8(offset);
10    const length = dv.getUint16(offset + 1, false); // big-endian
11    offset += 3;
12    if (offset + length > dv.byteLength) break; // truncated
13    const payload = new Uint8Array(buffer, offset, length);
14    messages.push({ type, payload });
15    offset += length;
16  }
17
18  return messages;
19}
20
21// Example usage
22const buf = new ArrayBuffer(1 + 2 + 3 + 1 + 2 + 2); // two messages
23const dv = new DataView(buf);
24let off = 0;
25// first message: type=1, length=3, payload=[1,2,3]
26dv.setUint8(off, 1); dv.setUint16(off + 1, 3, false); off += 3;
27new Uint8Array(buf, off, 3).set([1, 2, 3]); off += 3;
28// second message: type=2, length=2, payload=[9,8]
29dv.setUint8(off, 2); dv.setUint16(off + 1, 2, false); off += 3;
30new Uint8Array(buf, off, 2).set([9, 8]);
31
32console.log(parseMessages(buf));
  • 在本例中,头部是用 DataView 读取的,有效载荷部分则用 Uint8Array 创建切片。字节序和缓冲区长度检查很重要。

Web API与TypedArray(示例:获取二进制数据)

这是一个使用TypedArray处理从网络请求获得的ArrayBuffer的典型示例。

 1// Example of fetching binary and mapping to typed array
 2async function fetchBinary(url: string) {
 3  const res = await fetch(url);
 4  const ab = await res.arrayBuffer();
 5  const view = new Uint8Array(ab);
 6  // process view...
 7  console.log("received bytes:", view.length);
 8  return view;
 9}
10
11// Usage (call in async context)
12// fetchBinary("/path/to/file.bin");
  • 你可以直接将通过Response.arrayBuffer()获得的ArrayBuffer传递给TypedArray。它可用于图像、音频或自定义二进制协议。

性能建议与常见陷阱

以下是使用TypedArray时需要了解的一些“性能建议”和“常见陷阱”:。

  • 避免不必要的复制 为高效处理大数据,可以通过subarray创建部分视图或在多个视图间共享同一个ArrayBuffer,从而减少不必要的复制。

  • 注意字节序 对于网络通信或文件格式,数据顺序(字节序)通常是指定好的。使用DataView可以在读写时显式指定字节序,从而防止未预期的解释错误。

  • 注意每种类型的数值范围 例如,Uint8只能表示0到255的值。如果输入负值,可能会发生截断或数值回绕,因此需要根据情况定义转换规则。

  • 考虑垃圾回收压力 频繁地重新创建大型的 ArrayBuffer 会增加内存管理的负担。在性能敏感的场合,可以考虑尽量复用已有的缓冲区来设计代码。

总结

TypedArray是一种快速、高效处理二进制数据的机制。通过将预留固定长度字节区的ArrayBuffer与用特定类型读写内容的TypedArrayDataView结合使用,可以灵活地进行二进制操作。根据你的使用场景,可以选择使用 subarray/sliceDataView,并在实现时注意字节序和数据的副本。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video