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(Transferable 傳遞)
在瀏覽器的 postMessage 中,你可以將 ArrayBuffer 當作「可轉移」(transferable)物件來傳送。可以將擁有權直接轉移而無需複製,進而避免複製的成本。
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等可轉移物件的擁有權。 - 轉移後,原本端的緩衝區會變成「分離狀態(detached)」,無法再被存取。使用
SharedArrayBuffer可讓多個執行緒同時存取,但此功能有安全性要求及環境限制。
Node.js 的操作方式(與 Buffer 之間的相互轉換)
在 Node.js 中,可以在 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)通常會拷貝一份資料,但部分情況會共用記憶體,需小心位移(offset)設定。
效能考量與最佳實踐
為了最佳化效能並高效運用 ArrayBuffer,有幾個重點需要注意。下面列舉並說明實務中推薦的方法。
-
減少複製次數 處理大量二進位資料時,建議使用
subarray(共用視圖)或 transferable 避免不必要的複製。 -
一次分配較大的緩衝區 反覆分配小型緩衝區會增加額外負擔。如果可能,請一次分配較大的緩衝區,並在需要時使用其中的部分。
-
明確指定位元組順序(Endianness) 處理多位元組資料時,請使用
DataView並明確指定位元組序。大多數網路協議預設使用大端序(big-endian)。
常見使用範例
ArrayBuffer 在瀏覽器與 Node.js 中皆廣泛應用。
- 解析與建立二進位協議(例如用
DataView處理標頭資訊) - 媒體資料處理,例如圖片、音訊資料(例如:
fetch(...).then(res => res.arrayBuffer())) - WebAssembly 共用記憶體(Wasm 記憶體就是基於
ArrayBuffer運作) - 將重工作委外給 Worker(以 transferable 傳遞
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 頻道。