本頁旨在介紹如何在 JavaScript 中使用二進位資料。Bun 實作了許多用於處理二進位資料的資料類型和工具,其中大多數是 Web 標準。任何 Bun 特有的 API 都會特別註明。
以下是一個快速「備忘單」,同時也是目錄。點擊左欄中的項目可跳轉到該章節。
TypedArray | 一系列提供類似 Array 介面以與二進位資料互動的類別。包括 Uint8Array 、Uint16Array 、Int8Array 等。 |
Buffer | Uint8Array 的子類別,實作了廣泛的便利方法。與此表中的其他元素不同,這是一個 Node.js API (Bun 實作了它)。它不能在瀏覽器中使用。 |
DataView | 一個類別,提供 get/set API,用於在特定的位元組偏移量將一些位元組寫入 ArrayBuffer 。常用於讀取或寫入二進位協定。 |
Blob | 二進位資料的唯讀 blob,通常代表一個檔案。具有 MIME type 、size 以及用於轉換為 ArrayBuffer 、ReadableStream 和字串的方法。 |
File | Blob 的子類別,代表一個檔案。具有 name 和 lastModified 時間戳記。Node.js v20 中有實驗性支援。 |
BunFile | 僅限 Bun。Blob 的子類別,代表磁碟上延遲載入的檔案。使用 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 能夠表示更大的數字)。 |
Uint8ClampedArray | 與 Uint8Array 相同,但在將值指派給元素時,會自動「鉗制」到 0-255 範圍。 |
下表示範了在使用不同類型化陣列類別檢視時,ArrayBuffer
中的位元組如何被解釋。
ArrayBuffer | 00000000 | 00000001 | 00000010 | 00000011 | 00000100 | 00000101 | 00000110 | 00000111 |
Uint8Array | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Uint16Array | 256 (1 * 256 + 0 ) | 770 (3 * 256 + 2 ) | 1284 (5 * 256 + 4 ) | 1798 (7 * 256 + 6 ) | ||||
Uint32Array | 50462976 | 117835012 | ||||||
BigUint64Array | 506097522914230528n |
從預先定義的 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 個位元組。為了實現這些,我們指定 byteOffset
為 0
,length
為 2
,這表示我們希望陣列容納的 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);
廣義而言,類型化陣列提供與常規陣列相同的方法,但有一些例外。例如,push
和 pop
在類型化陣列上不可用,因為它們需要調整底層 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
這些部分可以是 string
、ArrayBuffer
、TypedArray
、DataView
或其他 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
BunFile
是 Blob
的子類別,用於表示磁碟上延遲載入的檔案。與 File
類似,它新增了 name
和 lastModified
屬性。與 File
不同,它不需要將檔案載入到記憶體中。
const file = Bun.file("index.txt");
// => BunFile
File
僅限瀏覽器。Node.js 20 中的實驗性支援。
File
是 Blob
的子類別,它新增了 name
和 lastModified
屬性。它通常在瀏覽器中用於表示透過 <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 ReadableStream
和 WritableStream
。
建立一個簡單的可讀串流
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
僅當 DataView
的 byteLength
是 TypedArray
子類別的 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();