TypeScript中的ArrayBuffer
本文将介绍TypeScript中的ArrayBuffer。
我们将逐步讲解TypeScript中的ArrayBuffer,从基础到实际应用技巧。
YouTube Video
TypeScript中的ArrayBuffer
ArrayBuffer 是一个内置对象,表示用于二进制数据的“原始内存区域”。它表示一个固定长度的原始缓冲区,可以通过在其上创建TypedArray或DataView来进行读写操作。
ArrayBuffer的基本概念与特点
ArrayBuffer 是一个固定长度的字节序列。创建时需指定字节大小,之后长度不可更改。
1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
- 此代码创建一个16字节的空缓冲区。可以通过
byteLength属性检查大小。
TypedArray和DataView——用于操作缓冲区的视图
ArrayBuffer没有读写数据的能力。因此,实际的操作是通过TypedArray或DataView完成的,在访问数据时需要指定整数、浮点数等类型以及字节序。
1// Create a buffer and a Uint8Array view over it. Then write bytes and read them.
2const buffer = new ArrayBuffer(8);
3const u8 = new Uint8Array(buffer);
4
5u8[0] = 0x41; // 'A'
6u8[1] = 0x42; // 'B'
7console.log(u8); // Uint8Array(8) [ 65, 66, 0, 0, 0, 0, 0, 0 ]
Uint8Array是按字节访问的数组视图,可以像普通数组那样访问。如果在同一个ArrayBuffer上创建多个视图,它们将共享同一块内存进行读写。
DataView:按任意字节位置和字节序读写
DataView 适用于按字节精细读写,并可指定大端或小端。
1// Using DataView to write/read multi-byte values with endianness control.
2const buf2 = new ArrayBuffer(8);
3const view = new DataView(buf2);
4
5// Write a 32-bit integer (little-endian)
6view.setInt32(0, 0x12345678, true);
7
8// Read it back as little-endian and big-endian
9const little = view.getInt32(0, true);
10const big = view.getInt32(0, false);
11console.log(little.toString(16)); // "12345678"
12console.log(big.toString(16)); // "78563412"
DataView允许你通过指定内存偏移量来读写数值,非常适合实现需要处理网络字节序的协议。
字符串与ArrayBuffer的转换(TextEncoder / TextDecoder)
要在文本和二进制之间转换,可以使用TextEncoder和TextDecoder。
1// Convert string -> ArrayBuffer and back using TextEncoder/TextDecoder.
2const encoder = new TextEncoder();
3const decoder = new TextDecoder();
4
5// Unicode escape sequences
6const str = "\u3053\u3093\u306B\u3061\u306F";
7const encoded = encoder.encode(str); // Uint8Array
8console.log(encoded); // Uint8Array([...])
9
10// If you need an ArrayBuffer specifically:
11const ab = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
12console.log(ab.byteLength); // bytes length of encoded text
13
14// Decode back
15const decoded = decoder.decode(encoded);
16console.log(decoded);TextEncoder.encode返回一个Uint8Array,如果你需要ArrayBuffer,应该访问它的.buffer属性。使用slice方法,你可以通过指定偏移和长度来提取所需的数据。
ArrayBuffer的切片与复制
slice方法返回一个新的ArrayBuffer。ArrayBuffer无法调整大小;如果需要更改大小,需要创建一个新的缓冲区并将数据复制进去。
1// Slice an ArrayBuffer and copy to a new sized buffer.
2const original = new Uint8Array([1,2,3,4,5]).buffer;
3const part = original.slice(1, 4); // bytes 1..3
4console.log(new Uint8Array(part)); // Uint8Array [ 2, 3, 4 ]
5
6// Resize: create a new buffer and copy existing content
7const larger = new ArrayBuffer(10);
8const target = new Uint8Array(larger);
9target.set(new Uint8Array(original), 0);
10console.log(target); // first bytes filled with original data
- 由于
slice方法会返回一个新的ArrayBuffer,如果你更重视效率或引用共享,可以使用TypedArray的subarray方法来引用同一个缓冲区。
TypedArray.subarray与复制的区别
TypedArray的subarray返回一个新的视图,但引用的是同一个ArrayBuffer。
1// subarray shares the same underlying buffer; modifying one affects the other.
2const arr = new Uint8Array([10,20,30,40]);
3const viewSub = arr.subarray(1,3); // shares memory
4viewSub[0] = 99;
5console.log(arr); // Uint8Array [10, 99, 30, 40]
- 共享视图可以避免复制的开销,但由于它们引用同一个缓冲区,需要注意副作用。
缓冲区拼接(组合多个ArrayBuffer)
ArrayBuffer长度固定,要合并多个缓冲区需新建一个ArrayBuffer并拷贝数据。
1// Concatenate multiple ArrayBuffers
2function concatBuffers(buffers: ArrayBuffer[]): ArrayBuffer {
3 const total = buffers.reduce((sum, b) => sum + b.byteLength, 0);
4 const result = new Uint8Array(total);
5 let offset = 0;
6 for (const b of buffers) {
7 const u8 = new Uint8Array(b);
8 result.set(u8, offset);
9 offset += u8.length;
10 }
11 return result.buffer;
12}
13
14const a = new Uint8Array([1,2]).buffer;
15const b = new Uint8Array([3,4,5]).buffer;
16const c = concatBuffers([a, b]);
17console.log(new Uint8Array(c)); // [1,2,3,4,5]
- 如果你经常需要拼接大量数据,提前计算总大小并只分配一次新的缓冲区(如本例代码所示),会更高效。
将ArrayBuffer传递给Worker(可转移对象)
在浏览器的postMessage中,可以将ArrayBuffer作为“可转移对象”传递。可以在不复制的情况下转移所有权,从而避免了复制的开销。
1// Example: posting an ArrayBuffer to a Worker as a transferable object (browser)
2const worker = new Worker('worker.js');
3const bufferToSend = new Uint8Array([1,2,3,4]).buffer;
4
5// Transfer ownership to the worker (main thread no longer owns it)
6worker.postMessage(bufferToSend, [bufferToSend]);
7
8// After transfer, bufferToSend.byteLength === 0 in many browsers (detached)
9console.log(bufferToSend.byteLength); // may be 0
- 通过将要转移的对象放在数组中作为
postMessage的第二个参数传递,可以无复制地转移ArrayBuffer等可转移对象的所有权。 - 转移后,缓冲区在原始端会变成“分离”状态,无法再访问。使用
SharedArrayBuffer可以让多个线程同时访问,但其使用有安全要求和环境限制。
在Node.js中的处理(与Buffer的互转)
在Node.js中,可以在Buffer与ArrayBuffer之间相互转换。这对于用TypeScript同时支持浏览器和Node.js非常有用。
1// Convert ArrayBuffer <-> Node.js Buffer
2// In Node.js environment:
3const ab = new Uint8Array([10,20,30]).buffer;
4const nodeBuffer = Buffer.from(ab); // ArrayBuffer -> Buffer
5console.log(nodeBuffer); // <Buffer 0a 14 1e>
6
7const backToAb = nodeBuffer.buffer.slice(
8 nodeBuffer.byteOffset,
9 nodeBuffer.byteOffset + nodeBuffer.byteLength
10);
11console.log(new Uint8Array(backToAb)); // Uint8Array [10,20,30]
- Node.js中的
Buffer.from(arrayBuffer)通常会创建副本,但在某些情况下可以共享引用,因此要注意偏移量。
性能问题与最佳实践
为优化性能并高效处理ArrayBuffer,有几个重要注意事项。下面列出并说明实际操作中的最佳实践。
-
减少复制次数 处理大二进制数据时,可用
subarray(共享视图)或可转移对象减少复制。 -
一次性分配大缓冲区 反复分配小缓冲区会增加开销。如果可能,一次性分配一个大缓冲区,根据需要使用其中的部分。
-
明确指定字节序 处理多字节值时,使用
DataView并显式指定字节序。网络协议通常使用大端字节序。
常见使用场景示例
ArrayBuffer在浏览器和Node.js中被广泛使用。
- 解析与构建二进制协议(用
DataView处理头信息) - 媒体处理,如图像与音频数据(
fetch(...).then(res => res.arrayBuffer())) - WebAssembly共享内存(Wasm的内存基于
ArrayBuffer) - 将重处理任务转交给Worker(以可转移对象传递
ArrayBuffer)
下面的代码是获取和分析二进制数据的示例。
1// Example: fetch binary data in browser and inspect first bytes
2async function fetchAndInspect(url: string) {
3 const resp = await fetch(url);
4 const ab = await resp.arrayBuffer();
5 const u8 = new Uint8Array(ab, 0, Math.min(16, ab.byteLength));
6 console.log('first bytes:', u8);
7}- 该代码从浏览器中的任意URL获取二进制数据,并显示前几个字节。代码通过
fetchAPI将数据加载为ArrayBuffer,并用Uint8Array检查前16个字节。
总结
ArrayBuffer是原始内存表示,可通过TypedArray和DataView实现高效二进制操作。通过避免不必要的复制和明确指定字节序,可以实现安全且高性能的二进制处理。
您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。