JavaScript ArrayBuffer, двоичные массивы
JavaScript предоставляет ArrayBuffer и типизированные массивы для работы с двоичными данными: файлами, изображениями, аудио и WebSocket-ответами.
В большинстве случаев JavaScript работает с текстом и JSON, но как только вы начинаете работать с файлами, изображениями, аудио, WebSocket-ами или ответами fetch, вы имеете дело с сырыми байтами. JavaScript обрабатывает эти байты через ArrayBuffer и семейство представлений типизированных массивов. В этой главе объясняется, что представляет собой каждый элемент, как они связаны между собой и когда их использовать — с запускаемыми примерами, которые вы можете проверить самостоятельно.
Мысленная модель состоит из двух уровней:
ArrayBuffer— блок сырой памяти фиксированной длины. Это просто байты; читать и записывать их напрямую невозможно.- Представления (типизированные массивы и
DataView) — объекты, которые интерпретируют эти байты как числа. Чтение и запись всегда выполняются через представление.
Что такое ArrayBuffer?
ArrayBuffer — это универсальный контейнер сырых двоичных данных фиксированной длины: блок памяти, измеряемый в байтах. У него нет понятия «элементов» или типов; он знает только, сколько байт содержит. Размер задаётся при создании и не может быть изменён.
Чтобы что-то сделать с этими байтами, нужно обернуть буфер в представление.
Типизированные массивы: представления над буфером
Типизированный массив — это представление, которое интерпретирует буфер как массив чисел фиксированного типа. Индексирование, итерация и арифметика работают так же, как в обычном array, но данные хранятся в сырой памяти буфера, что делает типизированные массивы компактными и быстрыми для больших числовых наборов данных.
Тип представления определяет, сколько байт занимает каждый элемент:
| Представление | Байт на элемент | Диапазон значений |
|---|---|---|
Uint8Array | 1 | 0 … 255 |
Int8Array | 1 | -128 … 127 |
Uint16Array | 2 | 0 … 65535 |
Int16Array | 2 | -32768 … 32767 |
Uint32Array | 4 | 0 … 4294967295 |
Int32Array | 4 | -2³¹ … 2³¹-1 |
Float32Array | 4 | 32-битное число с плавающей точкой |
Float64Array | 8 | 64-битное число с плавающей точкой |
Uint8ClampedArray | 1 | 0 … 255 (с ограничением) |
Создание типизированных массивов
Типизированный массив можно создать из длины, из существующего array или поверх существующего буфера.
Несколько представлений могут использовать один буфер
Это ключевая идея: несколько представлений могут находиться поверх одного и того же буфера. Запись через одно представление изменяет байты, и все остальные представления немедленно видят это изменение. Именно так можно интерпретировать одни и те же байты разными способами.
Переполнение и ограничение
Каждый элемент имеет фиксированную ширину, поэтому значения вне диапазона оборачиваются (берётся остаток от деления на размер типа). Uint8ClampedArray — исключение: он ограничивает значения диапазоном 0…255, что именно то, что нужно для пиксельных данных canvas.
Срезы и под-представления
slice() копирует данные в новый буфер, тогда как subarray() возвращает другое представление поверх той же памяти — дешевле, но записи разделяются.
DataView: смешанные типы и порядок байт
Типизированный массив навязывает один тип и использует нативный порядок байт платформы. DataView — низкоуровневая альтернатива: он позволяет читать и записывать разные типы по любому смещению байт и позволяет явно выбирать порядок байт (endianness). Этот контроль необходим при разборе форматов файлов и сетевых протоколов, где порядок байт фиксируется спецификацией, а не вашим процессором.
Endianness — это порядок, в котором хранится многобайтовое число: big-endian ставит старший байт первым, little-endian — последним. Каждый метод get/set у DataView принимает флаг littleEndian (по умолчанию используется big-endian).
Вот то же 32-битное число, записанное в обоих порядках байт, чтобы вы могли увидеть разницу:
Преобразование между байтами и другими типами
Двоичные данные редко существуют сами по себе — вы постоянно перемещаетесь между буферами, строками и объектами более высокого уровня.
Байты ↔ текст
Чтобы преобразовать string в байты (и обратно), используйте TextEncoder / TextDecoder, которые кодируют в UTF-8. Смотрите TextDecoder и TextEncoder для полного описания API.
Доступ к базовому буферу
Каждый типизированный массив предоставляет свой buffer, а также byteOffset и byteLength, описывающие охватываемый им регион. Именно так передаются байты в API, которому нужен ArrayBuffer.
Где реально используются двоичные массивы
- Файлы. Чтение файла как
ArrayBufferпозволяет проверять заголовки, разбирать форматы или загружать сырые байты. Смотрите Blob и File API. - Сеть.
fetch(...).arrayBuffer()и двоичные кадры WebSocket дают вамArrayBufferдля декодирования. - Canvas.
ctx.getImageData().data— этоUint8ClampedArrayбайтов пикселей RGBA, которые можно читать и перезаписывать. - WebGL / Web Audio. Вершинные буферы и аудиосэмплы представляют собой типизированные массивы для обеспечения производительности.
Пример: чтение файла как ArrayBuffer
В браузере FileReader (или более новый Blob.arrayBuffer()) считывает содержимое файла в буфер, который затем разбирается с помощью DataView или типизированного массива. Более подробное описание смотрите в File API.
function readBinaryFile(file) {
const reader = new FileReader();
reader.onload = () => {
const view = new DataView(reader.result);
console.log(view.getUint8(0)); // first byte, e.g. a format signature
};
reader.readAsArrayBuffer(file);
}
// Modern alternative:
// const buffer = await file.arrayBuffer();Рекомендации
- Выбирайте наименьший подходящий тип.
Uint8Arrayдля потоков байт,Float64Arrayдля точных вычислений — использование более широкого типа, чем нужно, расходует память впустую. - Используйте
DataViewтолько когда нужны смешанные типы или фиксированный порядок байт. Для однородного числового array типизированный массив проще и быстрее. - Переиспользуйте буферы в горячих циклах, чтобы снизить нагрузку на выделение памяти и сборщик мусора.
- Помните, что представления разделяют память — используйте
slice(), когда нужна независимая копия, иsubarray(), когда намеренно хотите создать псевдоним.
Итоги
ArrayBuffer— это сырая память фиксированной длины; вы никогда не обращаетесь к ней напрямую.- Типизированные массивы рассматривают эту память как числа одного фиксированного типа и ведут себя как array.
DataViewчитает и записывает смешанные типы по произвольным смещениям с явным указанием порядка байт — идеально подходит для разбора протоколов и форматов файлов.- Представления поверх одного буфера разделяют память, поэтому запись через одно видна через все остальные.
- Эти типы лежат в основе работы с файлами, сетевыми ответами, пикселями canvas, аудио и WebGL.