執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

二進位制資料

在 JavaScript 中處理二進位制資料

Bun 實現了多種用於在 JavaScript 中處理二進位制資料的資料型別和實用工具,其中大多數符合 Web 標準。Bun 特有的 API 會特別標註。

本速查表同時也是目錄;點選左列中的類即可跳轉到對應章節。

類描述
TypedArray一組提供類似 Array 介面、用於與二進位制資料互動的類。包括 Uint8Array、Uint16Array、Int8Array 等。
BufferUint8Array 的子類,實現了廣泛的便捷方法。與此表中的其他類不同,它是 Node.js API(Bun 已實現)。它在瀏覽器中不可用。
DataView提供 get/set API 的類,用於在 ArrayBuffer 的特定位元組偏移處讀寫一定數量的位元組。常用於讀取或寫入二進位制協議。
Blob通常表示檔案的只讀二進位制資料塊。具有 MIME type、size,以及用於轉換為 ArrayBuffer、ReadableStream 和字串的方法。
File表示檔案的 Blob 子類。具有 name 和 lastModified 時間戳。Node.js v20 提供了實驗性支援。
BunFile僅限 Bun。表示磁碟上延遲載入檔案的 Blob 子類。通過 Bun.file(path) 建立。

ArrayBuffer 和檢視

直到 ECMAScript 2015(ES6)引入一系列相關機制之前,JavaScript 都沒有語言原生的方式來儲存和操作二進位制資料。最基本的建置塊是 ArrayBuffer,一種表示記憶體中一系列位元組的資料結構。

TypeScript
// 此緩衝區可儲存 8 位元組
const buf = new ArrayBuffer(8);

儘管名稱中包含“陣列”(array),但它並不是陣列,也不支援你可能期待的任何陣列方法和運算子。你無法直接從 ArrayBuffer 中讀取或寫入值;你能做的只有檢查其大小,以及從中建立“切片”。

TypeScript
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8

const slice = buf.slice(0, 4); // 傳回新的 ArrayBuffer
slice.byteLength; // => 4

要讀取或寫入資料,你需要一個“檢視”:一種_封裝_ ArrayBuffer 例項,並允許你讀取和操作底層資料的類。檢視有兩種型別:_型別化陣列_和 DataView。

DataView

DataView 類是一個底層介面,用來讀取和操作 ArrayBuffer 中的資料。

以下程式碼建立一個 DataView,並將第一個位元組設定為 3。

TypeScript
const buf = new ArrayBuffer(4);
// [0b00000000, 0b00000000, 0b00000000, 0b00000000]

const dv = new DataView(buf);
dv.setUint8(0, 3); // 在位元組偏移量 0 寫入值 3
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]

接下來,在位元組偏移量 1 處寫入一個 Uint16。這需要兩個位元組。值 513 等於 2 * 256 + 1;用位元組表示就是 00000010 00000001。

TypeScript
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]

console.log(dv.getUint16(1)); // => 513

底層 ArrayBuffer 的前三個位元組現在都有值了。即使你使用 setUint16() 寫入了第二和第三個位元組,仍然可以使用 getUint8() 讀取每個組成位元組。

TypeScript
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1

如果寫入的值所需空間大於底層 ArrayBuffer 的可用空間,就會丟擲錯誤。以下程式碼嘗試在位元組偏移量 0 處寫入一個 Float64(需要 8 個位元組),但該緩衝區的長度只有 4 個位元組。

TypeScript
dv.setFloat64(0, 3.1415);
// ^ RangeError: 越界訪問

DataView 支援以下方法:

TypedArray

型別化陣列是一組類,提供類似陣列的介面,用於訪問 ArrayBuffer 中的資料。與 DataView 可以在某個偏移量寫入不同大小的數字不同,TypedArray 將底層位元組解釋為固定大小數字的陣列。

TypeScript
const buffer = new ArrayBuffer(3);
const arr = new Uint8Array(buffer);

// 內容初始化為零
console.log(arr); // Uint8Array(3) [0, 0, 0]

// 像陣列一樣賦值
arr[0] = 0;
arr[1] = 10;
arr[2] = 255;
arr[3] = 255; // 操作無效,越界

型別化陣列類,以及每種型別如何解釋 ArrayBuffer 中的位元組:

類描述
Uint8Array每個 1 位元組的資料都解釋為一個 8 位無符號整數。範圍為 0 到 255。
Uint16Array每兩個(2)位元組解釋為一個 16 位無符號整數。範圍為 0 到 65535。
Uint32Array每四個(4)位元組解釋為一個 32 位無符號整數。範圍為 0 到 4294967295。
Int8Array每個 1 位元組的資料都解釋為一個 8 位有符號整數。範圍為 -128 到 127。
Int16Array每兩個(2)位元組解釋為一個 16 位有符號整數。範圍為 -32768 到 32767。
Int32Array每四個(4)位元組解釋為一個 32 位有符號整數。範圍為 -2147483648 到 2147483647。
Float16Array每兩個(2)位元組解釋為一個 16 位浮點數。範圍為 -6.55e4 到 6.55e4。
Float32Array每四個(4)位元組解釋為一個 32 位浮點數。範圍為 -3.4e38 到 3.4e38。
Float64Array每八個(8)位元組解釋為一個 64 位浮點數。範圍為 -1.7e308 到 1.7e308。
BigInt64Array每八個(8)位元組解釋為一個有符號 BigInt。範圍為 -9223372036854775808 到 9223372036854775807(不過 BigInt 能表示更大的數字)。
BigUint64Array每八個(8)位元組解釋為一個無符號 BigInt。範圍為 0 到 18446744073709551615(不過 BigInt 能表示更大的數字)。
Uint8ClampedArray與 Uint8Array 相同,但為元素賦值時會自動將值“鉗制”在 0-255 範圍內。

下表展示了不同型別化陣列類如何解釋 ArrayBuffer 中的相同位元組。

從已有的 ArrayBuffer 建立型別化陣列:

TypeScript
// 從 ArrayBuffer 建立型別陣列
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);

arr[0] = 30;
arr[1] = 60;

// 所有元素初始化為 0
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];

從同一個 ArrayBuffer 例項化 Uint32Array 會丟擲錯誤。

TypeScript
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
//          ^  RangeError: ArrayBuffer 長度減去位元組偏移量
//             不是元素大小的倍數

一個 Uint32 值需要四個位元組(32 位)。由於 ArrayBuffer 的長度為 10 個位元組,因此無法將其內容整齊地劃分為每組 4 個位元組的資料塊。

要解決此問題,請在 ArrayBuffer 的特定“切片”上建立型別化陣列。下面的 Uint32Array 只“檢視”底層 ArrayBuffer 的前 8 個位元組:byteOffset 為 0,length 為 2,表示該陣列所包含的 Uint32 值的數量。

TypeScript
// 從 ArrayBuffer 切片建立型別化陣列
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);

/*
  buf    _ _ _ _ _ _ _ _ _ _    10 位元組
  arr   [_______,_______]       2 個 4 位元組元素
*/

arr.byteOffset; // 0
arr.length; // 2

你不需要先建立 ArrayBuffer 例項;可以直接將長度傳遞給型別化陣列建構函式:

TypeScript
const arr2 = new Uint8Array(5);

// 所有元素初始化為 0
// => Uint8Array(5) [0, 0, 0, 0, 0]

你也可以直接從數字陣列或另一個型別化陣列例項化型別化陣列:

TypeScript
// 從數字陣列
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;

// from another typed array
const arr2 = new Uint8Array(arr1);

型別化陣列提供與普通陣列相同的方法,但有少數例外。例如,push 和 pop 不可用,因為它們需要調整底層 ArrayBuffer 的大小。

TypeScript
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);

// supports common array methods
arr.filter(n => n > 5); // Uint8Array(2) [6, 7]
arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14]
arr.reduce((acc, n) => acc + n, 0); // 28
arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7
arr.every(n => n < 10); // true
arr.find(n => n > 5); // 6
arr.includes(5); // true
arr.indexOf(5); // 5

有關型別化陣列屬性和方法的更多資訊,請參閱 MDN 文件。

Uint8Array

Uint8Array 是 JavaScript 中最常見的型別化陣列。它表示經典的“位元組陣列”:由一系列介於 0 到 255 之間的 8 位無符號整陣列成。

在 Bun 中,它提供了在位元組陣列與其 base64 或十六進位制字串表示形式之間進行轉換的方法。

TypeScript
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBAU="
Uint8Array.fromBase64("AQIDBAU="); // Uint8Array(5) [1, 2, 3, 4, 5]

new Uint8Array([255, 254, 253, 252, 251]).toHex(); // "fffefdfcfb"
Uint8Array.fromHex("fffefdfcfb"); // Uint8Array(5) [255, 254, 253, 252, 251]

它是 TextEncoder#encode 的傳回值,也是 TextDecoder#decode 的輸入型別。這兩個實用工具類可在字串與各種二進位制編碼之間進行轉換,其中最常見的是 "utf-8"。

TypeScript
const encoder = new TextEncoder();
const bytes = encoder.encode("hello world");
// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]

const decoder = new TextDecoder();
const text = decoder.decode(bytes);
// => 你好世界

Buffer

Bun 實現了 Node.js 的 Buffer,這是一個在 JavaScript 標準中引入型別化陣列之前用於操作二進位制資料的 API。現在它已重新實現為 Uint8Array 子類,提供多種方法,包括陣列方法和類似 DataView 的方法。

TypeScript
const buf = Buffer.from("hello world");
// => Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]

buf.length; // => 11
buf[0]; // => 104,字元 'h' 的 ASCII 碼
buf.writeUInt8(72, 0); // 寫入字元 'H' 的 ASCII 碼

console.log(buf.toString());
// => Hello world

參見 Node.js 文件。

Blob

Blob 是一種通常用於表示檔案的 Web API。它最初源自瀏覽器(不同於屬於 JavaScript 本身的 ArrayBuffer),但 Node.js 和 Bun 也支援它。

你很少會直接建立 Blob 例項;它們通常來自外部來源(例如瀏覽器中的 <input type="file"> 元素)或某個庫。不過,你可以使用一個或多個字串或二進位制“Blob 部分”建立 Blob。

TypeScript
const blob = new Blob(["<html>Hello</html>"], {
  type: "text/html",
});

blob.type; // => text/html
blob.size; // => 18

這些部分可以是 string、ArrayBuffer、TypedArray、DataView 或其他 Blob 例項。建構函式會按你提供的順序連線這些部分。

TypeScript
const blob = new Blob([
  "<html>",
  new Blob(["<body>"]),
  new Uint8Array([104, 101, 108, 108, 111]), // 二進位制表示的 "hello"
  "</body></html>",
]);

以各種格式非同步讀取 Blob 的內容。

TypeScript
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (copies contents)
await blob.arrayBuffer(); // => ArrayBuffer (copies contents)
blob.stream(); // => ReadableStream

BunFile

BunFile 是 Blob 的子類,表示磁碟上延遲載入的檔案。與 File 一樣,它添加了 name 和 lastModified 屬性。與 File 不同,它不要求將檔案載入到記憶體中。

TypeScript
const file = Bun.file("index.txt");
// => BunFile

File

File 是 Blob 的子類,添加了 name 和 lastModified 屬性。在瀏覽器中,它通常用於表示通過 <input type="file"> 元素上傳的檔案。Node.js 和 Bun 都實現了 File。

TypeScript
// 瀏覽器環境
// <input type="file" id="file" />

const files = document.getElementById("file").files;
// => File[]
TypeScript
const file = new File(["<html>Hello</html>"], "index.html", {
  type: "text/html",
});

請參閱 MDN 文件。


流

流讓你能夠處理二進位制資料,而無需一次性將所有資料載入到記憶體中。它們通常用於讀取和寫入檔案、傳送和接收網路請求,以及處理大量資料。

Bun 實現了 Web API ReadableStream 和 WritableStream。

要建立可讀流:

TypeScript
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("hello");
    controller.enqueue("world");
    controller.close();
  },
});

使用 for await 逐塊讀取流。

TypeScript
for await (const chunk of stream) {
  console.log(chunk);
}

// => "hello"
// => "world"

有關 Bun 中流的更多資訊,請參見流。


格式轉換

使用本節作為將一種二進位制格式轉換為另一種格式的參考。

從 ArrayBuffer

由於 ArrayBuffer 儲存著其他二進位制結構(如 TypedArray)所相依的資料,以下程式碼片段並不是將 ArrayBuffer 轉換 為另一種格式。相反,它們使用底層資料_建立_一個新例項。

轉為 TypedArray

TypeScript
new Uint8Array(buf);

轉為 DataView

TypeScript
new DataView(buf);

轉為 Buffer

TypeScript
// 建立覆蓋整個 ArrayBuffer 的 Buffer
Buffer.from(buf);

// 建立覆蓋 ArrayBuffer 某個切片的 Buffer
Buffer.from(buf, 0, 10);

轉為字串

按 UTF-8 編碼:

TypeScript
new TextDecoder().decode(buf);

轉為數字陣列(number[])

TypeScript
Array.from(new Uint8Array(buf));

轉為 Blob

TypeScript
new Blob([buf], { type: "text/plain" });

轉為 ReadableStream

以下範例建立一個 ReadableStream,並將整個 ArrayBuffer 作為一個 chunk 入隊。

TypeScript
new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});
分塊讀取

要分塊流式傳輸 ArrayBuffer,使用 Uint8Array 檢視並逐塊入隊。

TypeScript
const view = new Uint8Array(buf);
const chunkSize = 1024;

new ReadableStream({
  start(controller) {
    for (let i = 0; i < view.length; i += chunkSize) {
      controller.enqueue(view.slice(i, i + chunkSize));
    }
    controller.close();
  },
});

從 TypedArray

轉為 ArrayBuffer

buffer 屬性是底層的 ArrayBuffer。TypedArray 可以是該緩衝區某個_切片_的檢視,因此兩者的大小可能不同。

TypeScript
arr.buffer;

轉為 DataView

要建立一個覆蓋與 TypedArray 相同位元組範圍的 DataView:

TypeScript
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);

轉為 Buffer

TypeScript
Buffer.from(arr);

轉為字串

按 UTF-8 編碼:

TypeScript
new TextDecoder().decode(arr);

轉為數字陣列(number[])

TypeScript
Array.from(arr);

轉為 Blob

TypeScript
new Blob([arr], { type: "text/plain" });

轉為 ReadableStream

TypeScript
new ReadableStream({
  start(controller) {
    controller.enqueue(arr);
    controller.close();
  },
});
分塊讀取

要分塊流式傳輸 ArrayBuffer,將型別化陣列劃分為多塊,分別入隊。

TypeScript
new ReadableStream({
  start(controller) {
    for (let i = 0; i < arr.length; i += chunkSize) {
      controller.enqueue(arr.slice(i, i + chunkSize));
    }
    controller.close();
  },
});

從 DataView

轉為 ArrayBuffer

TypeScript
view.buffer;

轉為 TypedArray

僅當 DataView 的 byteLength 是型別化陣列元素位元組大小的倍數時有效。

TypeScript
new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2);
new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4);
// 等等...

轉為 Buffer

TypeScript
Buffer.from(view.buffer, view.byteOffset, view.byteLength);

轉為字串

按 UTF-8 編碼:

TypeScript
new TextDecoder().decode(view);

轉為數字陣列(number[])

TypeScript
Array.from(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));

轉為 Blob

TypeScript
new Blob([view], { type: "text/plain" });

轉為 ReadableStream

TypeScript
new ReadableStream({
  start(controller) {
    controller.enqueue(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
    controller.close();
  },
});
分塊讀取

要分塊流式傳輸 ArrayBuffer,將 DataView 劃分為多塊,逐塊入隊。

TypeScript
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);

new ReadableStream({
  start(controller) {
    for (let i = 0; i < bytes.length; i += chunkSize) {
      controller.enqueue(bytes.slice(i, i + chunkSize));
    }
    controller.close();
  },
});

從 Buffer

轉為 ArrayBuffer

TypeScript
buf.buffer;

轉為 TypedArray

TypeScript
new Uint8Array(buf);

轉為 DataView

TypeScript
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);

轉為字串

按 UTF-8:

TypeScript
buf.toString();

按 base64:

TypeScript
buf.toString("base64");

按十六進位制:

TypeScript
buf.toString("hex");

轉為數字陣列(number[])

TypeScript
Array.from(buf);

轉為 Blob

TypeScript
new Blob([buf], { type: "text/plain" });

轉為 ReadableStream

TypeScript
new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});
分塊讀取

要分塊流式傳輸 ArrayBuffer,將 Buffer 劃分為多塊,逐塊入隊。

TypeScript
new ReadableStream({
  start(controller) {
    for (let i = 0; i < buf.length; i += chunkSize) {
      controller.enqueue(buf.slice(i, i + chunkSize));
    }
    controller.close();
  },
});

從 Blob

轉為 ArrayBuffer

TypeScript
await blob.arrayBuffer();

轉為 TypedArray

TypeScript
await blob.bytes();

轉為 DataView

TypeScript
new DataView(await blob.arrayBuffer());

轉為 Buffer

TypeScript
Buffer.from(await blob.arrayBuffer());

轉為字串

按 UTF-8:

TypeScript
await blob.text();

轉為數字陣列(number[])

TypeScript
Array.from(await blob.bytes());

轉為 ReadableStream

TypeScript
blob.stream();

從 ReadableStream

Response 是將 ReadableStream 轉換為其他格式時常用的中間型別。

TypeScript
stream; // ReadableStream

const buffer = await new Response(stream).arrayBuffer();

不過,這種方式較為冗長,並且會增加不必要的開銷。Bun 為將 ReadableStream 轉換為各種二進位制格式實現了經過最佳化的便捷函式。

轉為 ArrayBuffer

TypeScript
// 使用 Response
new Response(stream).arrayBuffer();

// 使用 Bun 函式
Bun.readableStreamToArrayBuffer(stream);

轉為 Uint8Array

TypeScript
// 使用 Response
new Response(stream).bytes();

// 使用 Bun 函式
Bun.readableStreamToBytes(stream);

轉為 TypedArray

TypeScript
// 使用 Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);

// with Bun function
new Int8Array(await Bun.readableStreamToArrayBuffer(stream));

轉為 DataView

TypeScript
// 使用 Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);

// with Bun function
new DataView(await Bun.readableStreamToArrayBuffer(stream));

轉為 Buffer

TypeScript
// 使用 Response
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);

// with Bun function
Buffer.from(await Bun.readableStreamToArrayBuffer(stream));

轉為字串

按 UTF-8:

TypeScript
// 使用 Response
await new Response(stream).text();

// 使用 Bun 函式
await Bun.readableStreamToText(stream);

轉為數字陣列(number[])

TypeScript
// 使用 Response
const arr = await new Response(stream).bytes();
Array.from(arr);

// with Bun function
Array.from(new Uint8Array(await Bun.readableStreamToArrayBuffer(stream)));

Bun 提供了用於將 ReadableStream 解析為塊陣列的工具。每個塊可能是字串、型別化陣列或 ArrayBuffer。

TypeScript
// 使用 Bun 函式
Bun.readableStreamToArray(stream);

轉為 Blob

TypeScript
new Response(stream).blob();

轉為 ReadableStream

要將一個 ReadableStream 拆分為兩個可以獨立使用的流:

TypeScript
const [a, b] = stream.tee();