Bun

二進位資料

本頁旨在介紹如何在 JavaScript 中使用二進位資料。Bun 實作了許多用於處理二進位資料的資料類型和工具,其中大多數是 Web 標準。任何 Bun 特有的 API 都會特別註明。

以下是一個快速「備忘單」,同時也是目錄。點擊左欄中的項目可跳轉到該章節。

TypedArray一系列提供類似 Array 介面以與二進位資料互動的類別。包括 Uint8ArrayUint16ArrayInt8Array 等。
BufferUint8Array 的子類別,實作了廣泛的便利方法。與此表中的其他元素不同,這是一個 Node.js API (Bun 實作了它)。它不能在瀏覽器中使用。
DataView一個類別,提供 get/set API,用於在特定的位元組偏移量將一些位元組寫入 ArrayBuffer。常用於讀取或寫入二進位協定。
Blob二進位資料的唯讀 blob,通常代表一個檔案。具有 MIME typesize 以及用於轉換為 ArrayBufferReadableStream 和字串的方法。
FileBlob 的子類別,代表一個檔案。具有 namelastModified 時間戳記。Node.js v20 中有實驗性支援。
BunFile僅限 BunBlob 的子類別,代表磁碟上延遲載入的檔案。使用 Bun.file(path) 建立。

ArrayBuffer 和視圖

在 2009 年之前,JavaScript 中沒有語言原生的方法來儲存和操作二進位資料。ECMAScript v5 為此引入了一系列新的機制。最基本的建構區塊是 ArrayBuffer,一個簡單的資料結構,表示記憶體中的位元組序列。

// this buffer can store 8 bytes
const buf = new ArrayBuffer(8);

儘管名稱如此,但它不是一個陣列,並且不支援人們可能期望的任何陣列方法和運算子。實際上,沒有辦法直接從 ArrayBuffer 讀取或寫入值。除了檢查其大小和從中建立「切片」之外,您幾乎無法對其執行任何操作。

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

const slice = buf.slice(0, 4); // returns new ArrayBuffer
slice.byteLength; // => 4

為了進行任何有趣的操作,我們需要一個稱為「視圖」的結構。視圖是一個類別,它*封裝*一個 ArrayBuffer 實例,並讓您讀取和操作底層資料。有兩種視圖類型:*類型化陣列*和 DataView

DataView

DataView 類別是用於讀取和操作 ArrayBuffer 中資料的更低階介面。

下面我們建立一個新的 DataView 並將第一個位元組設定為 3。

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

const dv = new DataView(buf);
dv.setUint8(0, 3); // write value 3 at byte offset 0
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]

現在讓我們在位元組偏移量 1 寫入一個 Uint16。這需要兩個位元組。我們使用值 513,它是 2 * 256 + 1;以位元組表示,它是 00000010 00000001

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

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

我們現在已將值指派給底層 ArrayBuffer 中的前三個位元組。即使第二個和第三個位元組是使用 setUint16() 建立的,我們仍然可以使用 getUint8() 讀取其每個組件位元組。

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

嘗試寫入需要比底層 ArrayBuffer 中可用空間更多的空間的值將會導致錯誤。下面我們嘗試在位元組偏移量 0 寫入一個 Float64 (需要 8 個位元組),但緩衝區中總共只有四個位元組。

dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds access

以下方法在 DataView 上可用

TypedArray

類型化陣列是一系列類別,提供類似 Array 的介面,用於與 ArrayBuffer 中的資料互動。DataView 允許您在特定偏移量寫入不同大小的數字,而 TypedArray 將底層位元組解釋為數字陣列,每個數字的大小固定。

注意 — 通常使用它們共用的父類別 TypedArray 來統稱這一系列類別。此類別在 JavaScript 中是*內部*的;您無法直接建立它的實例,並且 TypedArray 未在全域範圍中定義。將其視為 interface 或抽象類別。

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

// contents are initialized to zero
console.log(arr); // Uint8Array(3) [0, 0, 0]

// assign values like an array
arr[0] = 0;
arr[1] = 10;
arr[2] = 255;
arr[3] = 255; // no-op, out of bounds

雖然 ArrayBuffer 是一個通用的位元組序列,但這些類型化陣列類別將位元組解釋為給定位元組大小的數字陣列。 頂行包含原始位元組,後續行包含在使用不同類型化陣列類別*檢視*時如何解釋這些位元組。

以下類別是類型化陣列,以及它們如何解釋 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.104e5 到 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 能夠表示更大的數字)。
Uint8ClampedArrayUint8Array 相同,但在將值指派給元素時,會自動「鉗制」到 0-255 範圍。

下表示範了在使用不同類型化陣列類別檢視時,ArrayBuffer 中的位元組如何被解釋。

ArrayBuffer0000000000000001000000100000001100000100000001010000011000000111
Uint8Array01234567
Uint16Array256 (1 * 256 + 0) 770 (3 * 256 + 2) 1284 (5 * 256 + 4) 1798 (7 * 256 + 6)
Uint32Array50462976 117835012
BigUint64Array506097522914230528n

從預先定義的 ArrayBuffer 建立類型化陣列

// create typed array from ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);

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

// all elements are initialized to zero
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];

如果我們嘗試從同一個 ArrayBuffer 實例化 Uint32Array,我們會收到錯誤。

const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
//          ^  RangeError: ArrayBuffer length minus the byteOffset
//             is not a multiple of the element size

一個 Uint32 值需要四個位元組 (16 位元)。由於 ArrayBuffer 長度為 10 個位元組,因此無法將其內容乾淨地劃分為 4 位元組的區塊。

為了修正這個問題,我們可以在 ArrayBuffer 的特定「切片」上建立類型化陣列。下面的 Uint16Array 僅「檢視」底層 ArrayBuffer 的*前* 8 個位元組。為了實現這些,我們指定 byteOffset0length2,這表示我們希望陣列容納的 Uint16 數字的數量。

// create typed array from ArrayBuffer slice
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);

/*
  buf    _ _ _ _ _ _ _ _ _ _    10 bytes
  arr   [_______,_______]       2 4-byte elements
*/

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

您不需要明確建立 ArrayBuffer 實例;您可以改為在類型化陣列建構子中直接指定長度

const arr2 = new Uint8Array(5);

// all elements are initialized to zero
// => Uint8Array(5) [0, 0, 0, 0, 0]

類型化陣列也可以直接從數字陣列或另一個類型化陣列實例化

// from an array of numbers
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(arr);

廣義而言,類型化陣列提供與常規陣列相同的方法,但有一些例外。例如,pushpop 在類型化陣列上不可用,因為它們需要調整底層 ArrayBuffer 的大小。

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

// supports common array methods
arr.filter(n => n > 128); // Uint8Array(1) [255]
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,因為它代表經典的「位元組陣列」——介於 0 到 255 之間的 8 位元無符號整數序列。這是您在 JavaScript 中最常見的類型化陣列。

在 Bun 中,以及將來在其他 JavaScript 引擎中,它具有可用於在位元組陣列和這些陣列的序列化表示形式 (如 base64 或十六進位字串) 之間進行轉換的方法。

new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
Uint8Array.fromBase64("AQIDBA=="); // Uint8Array(4) [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"`。

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);
// => hello world

Buffer

Bun 實作了 Buffer,這是一個 Node.js API,用於處理早於 JavaScript 規範中類型化陣列引入的二進位資料。它後來被重新實作為 Uint8Array 的子類別。它提供了廣泛的方法,包括幾個類似陣列和類似 DataView 的方法。

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, ascii for 'h'
buf.writeUInt8(72, 0); // => ascii for 'H'

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

如需完整文件,請參閱 Node.js 文件

Blob

Blob 是一個 Web API,通常用於表示檔案。Blob 最初是在瀏覽器中實作的 (與作為 JavaScript 本身一部分的 ArrayBuffer 不同),但現在在 Node 和 Bun 中也支援。

直接建立 Blob 實例並不常見。更常見的情況是,您將從外部來源 (例如瀏覽器中的 <input type="file"> 元素) 或程式庫接收 Blob 實例。也就是說,可以從一個或多個字串或二進位「blob 部分」建立 Blob

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

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

這些部分可以是 stringArrayBufferTypedArrayDataView 或其他 Blob 實例。blob 部分按照它們提供的順序串聯在一起。

const blob = new Blob([
  "<html>",
  new Blob(["<body>"]),
  new Uint8Array([104, 101, 108, 108, 111]), // "hello" in binary
  "</body></html>",
]);

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

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

BunFile

BunFileBlob 的子類別,用於表示磁碟上延遲載入的檔案。與 File 類似,它新增了 namelastModified 屬性。與 File 不同,它不需要將檔案載入到記憶體中。

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

File

僅限瀏覽器。Node.js 20 中的實驗性支援。

FileBlob 的子類別,它新增了 namelastModified 屬性。它通常在瀏覽器中用於表示透過 <input type="file"> 元素上傳的檔案。Node.js 和 Bun 實作了 File

// on browser!
// <input type="file" id="file" />

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

如需完整文件資訊,請參閱 MDN 文件

串流

串流是處理二進位資料的重要抽象概念,無需一次將所有資料載入到記憶體中。它們通常用於讀寫檔案、發送和接收網路請求以及處理大量資料。

Bun 實作了 Web API ReadableStreamWritableStream

Bun 還實作了 node:stream 模組,包括 ReadableWritableDuplex。如需完整文件,請參閱 Node.js 文件。

建立一個簡單的可讀串流

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

此串流的內容可以使用 for await 語法逐區塊讀取。

for await (const chunk of stream) {
  console.log(chunk);
  // => "hello"
  // => "world"
}

有關 Bun 中串流的更完整討論,請參閱 API > 串流

轉換

從一種二進位格式轉換為另一種是常見的任務。本節旨在作為參考。

ArrayBuffer

由於 ArrayBuffer 儲存了其他二進位結構 (如 TypedArray) 的底層資料,因此下面的程式碼片段不是從 ArrayBuffer *轉換*為另一種格式。相反,它們是*建立*一個新實例,使用儲存在底層資料中的資料。

轉換為 TypedArray

new Uint8Array(buf);

轉換為 DataView

new DataView(buf);

轉換為 Buffer

// create Buffer over entire ArrayBuffer
Buffer.from(buf);

// create Buffer over a slice of the ArrayBuffer
Buffer.from(buf, 0, 10);

轉換為字串

作為 UTF-8

new TextDecoder().decode(buf);

轉換為 number[]

Array.from(new Uint8Array(buf));

轉換為 Blob

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

轉換為 ReadableStream

以下程式碼片段建立一個 ReadableStream,並將整個 ArrayBuffer 作為單個區塊排入佇列。

new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});

使用分塊

TypedArray

轉換為 ArrayBuffer

這會檢索底層 ArrayBuffer。請注意,TypedArray 可以是底層緩衝區*切片*的視圖,因此大小可能不同。

arr.buffer;

轉換為 DataView

建立一個 DataView,其位元組範圍與 TypedArray 相同。

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

轉換為 Buffer

Buffer.from(arr);

轉換為字串

作為 UTF-8

new TextDecoder().decode(arr);

轉換為 number[]

Array.from(arr);

轉換為 Blob

// only if arr is a view of its entire backing TypedArray
new Blob([arr.buffer], { type: "text/plain" });

轉換為 ReadableStream

new ReadableStream({
  start(controller) {
    controller.enqueue(arr);
    controller.close();
  },
});

使用分塊

DataView

轉換為 ArrayBuffer

view.buffer;

轉換為 TypedArray

僅當 DataViewbyteLengthTypedArray 子類別的 BYTES_PER_ELEMENT 的倍數時才有效。

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);
// etc...

轉換為 Buffer

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

轉換為字串

作為 UTF-8

new TextDecoder().decode(view);

轉換為 number[]

Array.from(view);

轉換為 Blob

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

轉換為 ReadableStream

new ReadableStream({
  start(controller) {
    controller.enqueue(view.buffer);
    controller.close();
  },
});

使用分塊

Buffer

轉換為 ArrayBuffer

buf.buffer;

轉換為 TypedArray

new Uint8Array(buf);

轉換為 DataView

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

轉換為字串

作為 UTF-8

buf.toString();

作為 base64

buf.toString('base64');

作為十六進位

buf.toString('hex');

轉換為 number[]

Array.from(buf);

轉換為 Blob

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

轉換為 ReadableStream

new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});

使用分塊

Blob

轉換為 ArrayBuffer

Blob 類別為此目的提供了一個便利方法。

await blob.arrayBuffer();

轉換為 TypedArray

await blob.bytes();

轉換為 DataView

new DataView(await blob.arrayBuffer());

轉換為 Buffer

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

轉換為字串

作為 UTF-8

await blob.text();

轉換為 number[]

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

轉換為 ReadableStream

blob.stream();

ReadableStream

通常使用 Response 作為方便的中間表示形式,以便更輕鬆地將 ReadableStream 轉換為其他格式。

stream; // ReadableStream

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

然而,這種方法很冗長,並且增加了不必要的額外負荷,從而降低了整體效能。Bun 實作了一組最佳化的便利函式,用於將 ReadableStream 轉換為各種二進位格式。

轉換為 ArrayBuffer

// with Response
new Response(stream).arrayBuffer();

// with Bun function
Bun.readableStreamToArrayBuffer(stream);

轉換為 Uint8Array

// with Response
new Response(stream).bytes();

// with Bun function
Bun.readableStreamToBytes(stream);

轉換為 TypedArray

// with Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);

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

轉換為 DataView

// with Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);

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

轉換為 Buffer

// with Response
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);

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

轉換為字串

作為 UTF-8

// with Response
await new Response(stream).text();

// with Bun function
await Bun.readableStreamToText(stream);

轉換為 number[]

// with Response
const arr = await new Response(stream).bytes();
Array.from(arr);

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

Bun 提供了一個實用程式,用於將 ReadableStream 解析為其區塊陣列。每個區塊可以是字串、類型化陣列或 ArrayBuffer

// with Bun function
Bun.readableStreamToArray(stream);

轉換為 Blob

new Response(stream).blob();

轉換為 ReadableStream

ReadableStream 分割成兩個可以獨立使用的串流

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