此頁面旨在作為在 JavaScript 中使用二進制資料的入門。Bun 實作了許多資料類型和公用程式,用於處理二進制資料,其中大部分是 Web 標準。任何 Bun 專屬的 API 都會註明。
以下是快速「秘笈」,同時也是目錄。按一下左欄中的項目,即可跳到該區段。
TypedArray | 提供類似 Array 的介面,用於與二進制資料互動的類別系列。包含 Uint8Array 、Uint16Array 、Int8Array 等。 |
Buffer | 實作各種便利方法的 Uint8Array 子類別。與此表格中的其他元素不同,這是 Node.js API(Bun 實作)。它無法在瀏覽器中使用。 |
DataView | 提供 get/set API 的類別,用於在特定位元組偏移量將一些位元組寫入 ArrayBuffer 。通常用於讀取或寫入二進制通訊協定。 |
Blob | 一個唯讀的二進制資料 blob,通常代表一個檔案。具有一個 MIME 類型 、一個 大小 ,以及轉換為 ArrayBuffer 、ReadableStream 和字串的方法。 |
檔案 | Blob 的一個子類別,代表一個檔案。具有 名稱 和 最後修改 時間戳記。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
,並將第一個位元組設定為 5。
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
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。 |
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
,這表示我們希望陣列包含的 Uint32
數字數量。
// 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 中會遇到的最常見的類型化陣列。
這是 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(16) [ 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 115, 116, 114, 105, 110, 103 ]
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
最初在瀏覽器中實作(與 ArrayBuffer
不同,ArrayBuffer
是 JavaScript 本身的一部分),但現在在 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.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);
轉換為 字串
new TextDecoder().decode(buf);
轉換為 數字陣列
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
在與 TypedArray 相同的位元組範圍上建立 DataView
。
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
轉換為 Buffer
Buffer.from(arr);
轉換為 字串
new TextDecoder().decode(arr);
轉換為 數字陣列
Array.from(arr);
轉換為 Blob
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);
轉換為 字串
new TextDecoder().decode(view);
轉換為 數字陣列
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);
轉換為 字串
buf.toString();
轉換為 數字陣列
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
new Uint8Array(await blob.arrayBuffer());
轉換為 DataView
new DataView(await blob.arrayBuffer());
轉換為 Buffer
Buffer.from(await blob.arrayBuffer());
轉換為 字串
await blob.text();
轉換為 數字陣列
Array.from(new Uint8Array(await blob.arrayBuffer()));
轉換為 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);
轉換為 TypedArray
// with Response
const buf = await new Response(stream).arrayBuffer();
new Uint8Array(buf);
// with Bun function
new Uint8Array(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));
轉換為 字串
// with Response
new Response(stream).text();
// with Bun function
await Bun.readableStreamToText(stream);
轉換為 數字陣列
// with Response
const buf = await new Response(stream).arrayBuffer();
Array.from(new Uint8Array(buf));
// 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();