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本身没有读写功能,因此需要通过TypedArray或DataView进行访问。
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(如Uint8Array、Int16Array、Float32Array等)
TypedArray在ArrayBuffer之上创建一个“类型化视图”。以下是一些典型的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),在实现二进制协议时非常有用。
subarray与slice的区别
TypedArray的subarray返回共享原始缓冲区的视图,而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与用特定类型读写内容的TypedArray或DataView结合使用,可以灵活地进行二进制操作。根据你的使用场景,可以选择使用 subarray/slice 或 DataView,并在实现时注意字节序和数据的副本。
您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。