Эта страница предназначена как введение в работу с двоичными данными в JavaScript. Bun реализует ряд типов данных и утилит для работы с двоичными данными, большинство из которых являются веб-стандартами. Любые API, специфичные для Bun, будут отмечены.
Ниже приведена быстрая «шпаргалка», которая также служит оглавлением. Нажмите на элемент в левом столбце, чтобы перейти к этому разделу.
| Класс | Описание |
|---|---|
TypedArray | Семейство классов, предоставляющих интерфейс, подобный Array, для взаимодействия с двоичными данными. Включает Uint8Array, Uint16Array, Int8Array и другие. |
Buffer | Подкласс Uint8Array, реализующий широкий спектр вспомогательных методов. В отличие от других элементов в этой таблице, это API Node.js (который реализует Bun). Не может использоваться в браузере. |
DataView | Класс, предоставляющий API get/set для записи некоторого количества байтов в ArrayBuffer по определённому смещению в байтах. Часто используется для чтения или записи двоичных протоколов. |
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 — простая структура данных, представляющая последовательность байтов в памяти.
// этот буфер может хранить 8 байт
const buf = new ArrayBuffer(8);Несмотря на название, это не массив и не поддерживает ни один из методов и операторов массива, которых можно было бы ожидать. На самом деле, нет способа напрямую читать или записывать значения из ArrayBuffer. С ним можно сделать очень мало, кроме проверки его размера и создания «срезов» из него.
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8
const slice = buf.slice(0, 4); // возвращает новый ArrayBuffer
slice.byteLength; // => 4Чтобы сделать что-то интересное, нам нужна конструкция, известная как «представление» (view). Представление — это класс, который оборачивает экземпляр 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); // записать значение 3 по смещению байта 0
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]Теперь запишем Uint16 по смещению байта 1. Это требует два байта. Мы используем значение 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, вызовет ошибку. Ниже мы пытаемся записать Float64 (который требует 8 байтов) по смещению байта 0, но в буфере всего четыре байта.
dv.setFloat64(0, 3.1415);
// ^ RangeError: Доступ за пределами диапазонаСледующие методы доступны в DataView:
TypedArray
Типизированные массивы — это семейство классов, предоставляющих интерфейс, подобный Array, для взаимодействия с данными в ArrayBuffer. В то время как DataView позволяет записывать числа разного размера по определённому смещению, TypedArray интерпретирует базовые байты как массив чисел, каждое фиксированного размера.
NOTE
Обычно это семейство классов collectively называют их общим суперклассом `TypedArray`. Этот класс является _внутренним_ для JavaScript; вы не можете напрямую создавать его экземпляры, и `TypedArray` не определён в глобальной области. Думайте о нём как об `interface` или абстрактном классе.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 — это последовательность байтов, эти классы типизированных массивов интерпретируют байты как массив чисел заданного размера в байтах. Верхняя строка содержит необработанные байты, а последующие строки содержат то, как эти байты будут интерпретироваться при представлении с помощью различных классов типизированных массивов.
Следующие классы являются типизированными массивами вместе с описанием того, как они интерпретируют байты в 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 интерпретируются при представлении с помощью различных классов типизированных массивов.
| Байт 0 | Байт 1 | Байт 2 | Байт 3 | Байт 4 | Байт 5 | Байт 6 | Байт 7 | |
|---|---|---|---|---|---|---|---|---|
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:
// создать типизированный массив из ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);
arr[0] = 30;
arr[1] = 60;
// все элементы инициализируются нулями
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];Если бы мы попытались создать Uint32Array из этого же ArrayBuffer, мы бы получили ошибку.
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
// ^ RangeError: Длина ArrayBuffer минус byteOffset
// не кратна размеру элементаЗначение Uint32 требует четыре байта (16 бит). Поскольку ArrayBuffer имеет длину 10 байтов, нет способа чисто разделить его содержимое на 4-байтовые блоки.
Чтобы исправить это, мы можем создать типизированный массив над определённым «срезом» ArrayBuffer. Uint16Array ниже только «представляет» первые 8 байтов базового ArrayBuffer. Для этого мы указываем byteOffset равным 0 и length равным 2, что указывает количество чисел Uint32, которые должен содержать наш массив.
// создать типизированный массив из среза 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; вы можете напрямую указать длину в конструкторе типизированного массива:
const arr2 = new Uint8Array(5);
// все элементы инициализируются нулями
// => Uint8Array(5) [0, 0, 0, 0, 0]Типизированные массивы также могут быть созданы напрямую из массива чисел или другого типизированного массива:
// из массива чисел
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;
// из другого типизированного массива
const arr2 = new Uint8Array(arr);В целом, типизированные массивы предоставляют те же методы, что и обычные массивы, за некоторыми исключениями. Например, push и pop недоступны в типизированных массивах, потому что они потребовали бы изменения размера базового ArrayBuffer.
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
// поддерживает распространённые методы массивов
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, так как он представляет классический «байтовый массив» — последовательность 8-битных беззнаковых целых чисел от 0 до 255. Это наиболее распространённый типизированный массив, с которым вы столкнётесь в JavaScript.
В Bun и однажды в других движках JavaScript он имеет методы для преобразования между байтовыми массивами и сериализованными представлениями этих массивов в виде строк base64 или hex.
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 — двух классов утилит, предназначенных для перевода строк и различных двоичных кодировок, наиболее notably "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 worldBuffer
Bun реализует Buffer — API Node.js для работы с двоичными данными, который появился до введения типизированных массивов в спецификации JavaScript. С тех пор он был переделан как подкласс Uint8Array. Он предоставляет широкий спектр методов, включая несколько методов, подобных Array и 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 для 'h'
buf.writeUInt8(72, 0); // => ascii для 'H'
console.log(buf.toString());
// => Hello worldПолную документацию см. в документации Node.js.
Blob
Blob — это веб-API, обычно используемый для представления файлов. Blob изначально был реализован в браузерах (в отличие от ArrayBuffer, который является частью самого JavaScript), но теперь поддерживается в Node и Bun.
Необычно напрямую создавать экземпляры Blob. Чаще вы будете получать экземпляры Blob из внешнего источника (например, элемента <input type="file"> в браузере) или библиотеки. Тем не менее, можно создать Blob из одной или нескольких строковых или двоичных «частей блоба».
const blob = new Blob(["<html>Hello</html>"], {
type: "text/html",
});
blob.type; // => text/html
blob.size; // => 19Эти части могут быть string, ArrayBuffer, TypedArray, DataView или другими экземплярами Blob. Части блоба объединяются в порядке их предоставления.
const blob = new Blob([
"<html>",
new Blob(["<body>"]),
new Uint8Array([104, 101, 108, 108, 111]), // "hello" в двоичном виде
"</body></html>",
]);Содержимое Blob может быть асинхронно прочитано в различных форматах.
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (копирует содержимое)
await blob.arrayBuffer(); // => ArrayBuffer (копирует содержимое)
await blob.stream(); // => ReadableStreamBunFile
BunFile — это подкласс Blob, используемый для представления лениво загружаемого файла на диске. Как и File, он добавляет свойства name и lastModified. В отличие от File, он не требует загрузки файла в память.
const file = Bun.file("index.txt");
// => BunFileFile
File — это подкласс Blob, который добавляет свойства name и lastModified. Обычно используется в браузере для представления файлов, загруженных через элемент <input type="file">. Node.js и Bun реализуют File.
// в браузере!
// <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 реализует веб-API ReadableStream и WritableStream.
NOTE
Bun также реализует модуль `node:stream`, включая [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams) и [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). Полную документацию см. в документации 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
// создать Buffer над всем ArrayBuffer
Buffer.from(buf);
// создать Buffer над срезом ArrayBuffer
Buffer.from(buf, 0, 10);В string
Как 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();
},
});С чанкованием
Для потоковой передачи ArrayBuffer по частям используйте представление Uint8Array и помещайте каждый чанк.
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
Это извлекает базовый ArrayBuffer. Обратите внимание, что TypedArray может быть представлением среза базового буфера, поэтому размеры могут отличаться.
arr.buffer;В DataView
Для создания DataView над тем же диапазоном байтов, что и TypedArray.
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);В Buffer
Buffer.from(arr);В string
Как UTF-8:
new TextDecoder().decode(arr);В number[]
Array.from(arr);В Blob
// только если arr является представлением всего своего базового TypedArray
new Blob([arr.buffer], { type: "text/plain" });В ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});С чанкованием
Для потоковой передачи ArrayBuffer по частям разделите TypedArray на чанки и помещайте каждый по отдельности.
new ReadableStream({
start(controller) {
for (let i = 0; i < arr.length; i += chunkSize) {
controller.enqueue(arr.slice(i, i + chunkSize));
}
controller.close();
},
});Из DataView
В ArrayBuffer
view.buffer;В TypedArray
Работает только если byteLength DataView кратен BYTES_PER_ELEMENT подкласса TypedArray.
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
Buffer.from(view.buffer, view.byteOffset, view.byteLength);В string
Как 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();
},
});С чанкованием
Для потоковой передачи ArrayBuffer по частям разделите DataView на чанки и помещайте каждый по отдельности.
new ReadableStream({
start(controller) {
for (let i = 0; i < view.byteLength; i += chunkSize) {
controller.enqueue(view.buffer.slice(i, i + chunkSize));
}
controller.close();
},
});Из Buffer
В ArrayBuffer
buf.buffer;В TypedArray
new Uint8Array(buf);В DataView
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);В string
Как UTF-8:
buf.toString();Как base64:
buf.toString("base64");Как hex:
buf.toString("hex");В number[]
Array.from(buf);В Blob
new Blob([buf], { type: "text/plain" });В ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});С чанкованием
Для потоковой передачи ArrayBuffer по частям разделите Buffer на чанки и помещайте каждый по отдельности.
new ReadableStream({
start(controller) {
for (let i = 0; i < buf.length; i += chunkSize) {
controller.enqueue(buf.slice(i, i + chunkSize));
}
controller.close();
},
});Из Blob
В ArrayBuffer
Класс Blob предоставляет удобный метод для этой цели.
await blob.arrayBuffer();В TypedArray
await blob.bytes();В DataView
new DataView(await blob.arrayBuffer());В Buffer
Buffer.from(await blob.arrayBuffer());В string
Как 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();Однако этот подход многословен и добавляет накладные расходы, которые unnecessarily замедляют общую производительность. Bun реализует набор оптимизированных вспомогательных функций для преобразования ReadableStream в различные двоичные форматы.
В ArrayBuffer
// с Response
new Response(stream).arrayBuffer();
// с функцией Bun
Bun.readableStreamToArrayBuffer(stream);В Uint8Array
// с Response
new Response(stream).bytes();
// с функцией Bun
Bun.readableStreamToBytes(stream);В TypedArray
// с Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);
// с функцией Bun
new Int8Array(Bun.readableStreamToArrayBuffer(stream));В DataView
// с Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);
// с функцией Bun
new DataView(Bun.readableStreamToArrayBuffer(stream));В Buffer
// с Response
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);
// с функцией Bun
Buffer.from(Bun.readableStreamToArrayBuffer(stream));В string
Как UTF-8:
// с Response
await new Response(stream).text();
// с функцией Bun
await Bun.readableStreamToText(stream);В number[]
// с Response
const arr = await new Response(stream).bytes();
Array.from(arr);
// с функцией Bun
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));Bun предоставляет утилиту для разрешения ReadableStream в массив его чанков. Каждый чанк может быть строкой, типизированным массивом или ArrayBuffer.
// с функцией Bun
Bun.readableStreamToArray(stream);В Blob
new Response(stream).blob();В ReadableStream
Для разделения ReadableStream на два потока, которые могут потребляться независимо:
const [a, b] = stream.tee();