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 等)

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 位元整數讀取相同區域。請注意,輸出結果會根據執行環境的位元組序(endianness)而有所不同。

位元組序(Endianness)與 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 是一個方便用於細緻讀寫位元組的機制。它支援各種型別,例如有號或無號整數、浮點數,並可明確指定位元組序(Endianness),實作二進位協議時非常實用。

subarray 與 slice 的差異

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 位元組資料長度與後續內容(payload)組成的固定格式二進位資料。

 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,以減少不必要的複製。

  • 注意位元組序(Endianness) 多數網路通訊或檔案格式都指定了資料的位元組順序(byte order)。使用 DataView 可在讀寫時明確指定位元組序,避免誤解資料內容。

  • 注意各型別的數值範圍 例如,Uint8 只能表示 0 到 255 的值。如果你輸入負值,可能會發生截斷或數值溢位,因此應該根據需要定義轉換規則。

  • 考慮垃圾回收壓力 頻繁地重新建立大型的 ArrayBuffer 會增加記憶體管理的負擔。在對效能要求嚴格的情況下,你可以考慮設計程式碼以盡可能重複使用現有的緩衝區。

總結

TypedArray 是一種快速高效處理二進位資料的機制。結合 ArrayBuffer(預留固定長度的位元組區域)與 TypedArrayDataView(以特定型別讀寫資料),可以靈活進行二進位操作。根據你的使用情境,可以選擇使用 subarray/sliceDataView,並在設計實作時注意位元組順序與複製的問題。

您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。

YouTube Video