ArrayBuffer в TypeScript

ArrayBuffer в TypeScript

В этой статье объясняется, что такое ArrayBuffer в TypeScript.

Мы шаг за шагом объясним, что такое ArrayBuffer в TypeScript, охватывая основы и практические техники.

YouTube Video

ArrayBuffer в TypeScript

ArrayBuffer — это встроенный объект, который представляет собой «сырой участок памяти» для бинарных данных. Он представляет собой буфер фиксированной длины, поверх которого для чтения и записи используются TypedArray или DataView.

Основные понятия и характеристики ArrayBuffer

ArrayBuffer — это последовательность байтов фиксированной длины. Размер в байтах указывается при создании, и изменить его впоследствии нельзя.

1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
  • Этот код создаёт пустой буфер размером 16 байт. Размер можно узнать с помощью свойства byteLength.

TypedArray и DataView — представления для управления буферами

ArrayBuffer не имеет возможности читать или записывать данные. Поэтому реальные операции выполняются с помощью TypedArray или DataView, где при доступе к данным указывается тип (например, целое число или число с плавающей запятой) и порядок байтов (эндианность).

1// Create a buffer and a Uint8Array view over it. Then write bytes and read them.
2const buffer = new ArrayBuffer(8);
3const u8 = new Uint8Array(buffer);
4
5u8[0] = 0x41; // 'A'
6u8[1] = 0x42; // 'B'
7console.log(u8); // Uint8Array(8) [ 65, 66, 0, 0, 0, 0, 0, 0 ]
  • Uint8Array — это отображение массива по байтам, к которому можно обращаться как к обычному массиву. Если вы используете несколько представлений одного и того же ArrayBuffer, они используют одну и ту же память для чтения и записи.

DataView: чтение и запись с произвольными границами и порядком байт

DataView полезен для поразрядного чтения и записи и позволяет явно указывать порядок байт (little-endian или big-endian).

 1// Using DataView to write/read multi-byte values with endianness control.
 2const buf2 = new ArrayBuffer(8);
 3const view = new DataView(buf2);
 4
 5// Write a 32-bit integer (little-endian)
 6view.setInt32(0, 0x12345678, true);
 7
 8// Read it back as little-endian and big-endian
 9const little = view.getInt32(0, true);
10const big = view.getInt32(0, false);
11console.log(little.toString(16)); // "12345678"
12console.log(big.toString(16));    // "78563412"
  • DataView позволяет читать и записывать значения, указывая смещения в памяти, что делает его подходящим для реализации протоколов, требующих обработки сетевого порядка байтов.

Преобразование между строками и ArrayBuffer (TextEncoder / TextDecoder)

Для преобразования текста в двоичный формат и обратно используйте TextEncoder и TextDecoder.

 1// Convert string -> ArrayBuffer and back using TextEncoder/TextDecoder.
 2const encoder = new TextEncoder();
 3const decoder = new TextDecoder();
 4
 5// Unicode escape sequences
 6const str = "\u3053\u3093\u306B\u3061\u306F";
 7const encoded = encoder.encode(str); // Uint8Array
 8console.log(encoded); // Uint8Array([...])
 9
10// If you need an ArrayBuffer specifically:
11const ab = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
12console.log(ab.byteLength); // bytes length of encoded text
13
14// Decode back
15const decoded = decoder.decode(encoded);
16console.log(decoded);
  • TextEncoder.encode возвращает Uint8Array, поэтому если вам нужен ArrayBuffer, обратитесь к его свойству .buffer. С помощью метода slice вы можете извлечь необходимые данные, указав смещение и длину.

Обрезка и копирование ArrayBuffer

Метод slice возвращает новый ArrayBuffer. ArrayBuffer не может менять свой размер; если необходимо изменить размер, создайте новый буфер и скопируйте данные в него.

 1// Slice an ArrayBuffer and copy to a new sized buffer.
 2const original = new Uint8Array([1,2,3,4,5]).buffer;
 3const part = original.slice(1, 4); // bytes 1..3
 4console.log(new Uint8Array(part)); // Uint8Array [ 2, 3, 4 ]
 5
 6// Resize: create a new buffer and copy existing content
 7const larger = new ArrayBuffer(10);
 8const target = new Uint8Array(larger);
 9target.set(new Uint8Array(original), 0);
10console.log(target); // first bytes filled with original data
  • Поскольку метод slice возвращает новый ArrayBuffer, если для вас важна эффективность или совместное использование ссылок, вы можете использовать метод subarray у TypedArray для ссылки на тот же буфер.

Различие между TypedArray.subarray и копированием

subarray от TypedArray возвращает новое представление, ссылающееся на тот же ArrayBuffer.

1// subarray shares the same underlying buffer; modifying one affects the other.
2const arr = new Uint8Array([10,20,30,40]);
3const viewSub = arr.subarray(1,3); // shares memory
4viewSub[0] = 99;
5console.log(arr); // Uint8Array [10, 99, 30, 40]
  • Общие представления позволяют избежать затрат на копирование, но, поскольку они ссылаются на один и тот же буфер, необходимо быть осторожным с побочными эффектами.

Объединение буферов (соединение нескольких ArrayBuffer)

Поскольку ArrayBuffer имеет фиксированную длину, для объединения нескольких буферов создайте новый ArrayBuffer и скопируйте данные.

 1// Concatenate multiple ArrayBuffers
 2function concatBuffers(buffers: ArrayBuffer[]): ArrayBuffer {
 3  const total = buffers.reduce((sum, b) => sum + b.byteLength, 0);
 4  const result = new Uint8Array(total);
 5  let offset = 0;
 6  for (const b of buffers) {
 7    const u8 = new Uint8Array(b);
 8    result.set(u8, offset);
 9    offset += u8.length;
10  }
11  return result.buffer;
12}
13
14const a = new Uint8Array([1,2]).buffer;
15const b = new Uint8Array([3,4,5]).buffer;
16const c = concatBuffers([a, b]);
17console.log(new Uint8Array(c)); // [1,2,3,4,5]
  • Если вы часто объединяете большие объемы данных, более эффективно заранее вычислить общий размер и выделить новый буфер только один раз, как показано в этом коде.

Передача ArrayBuffer в Worker (передаваемый объект)

В браузере через postMessage вы можете передать ArrayBuffer как «перемещаемый» (transferable) объект. Право владения может быть передано без копирования, что позволяет избежать затрат на копирование.

1// Example: posting an ArrayBuffer to a Worker as a transferable object (browser)
2const worker = new Worker('worker.js');
3const bufferToSend = new Uint8Array([1,2,3,4]).buffer;
4
5// Transfer ownership to the worker (main thread no longer owns it)
6worker.postMessage(bufferToSend, [bufferToSend]);
7
8// After transfer, bufferToSend.byteLength === 0 in many browsers (detached)
9console.log(bufferToSend.byteLength); // may be 0
  • Указав объекты для передачи в массиве в качестве второго аргумента функции postMessage, вы можете передать право владения (без копирования) такими перемещаемыми объектами, как ArrayBuffer.
  • После передачи буфер становится «отсоединённым» (detached) на исходной стороне и больше не доступен. Использование SharedArrayBuffer позволяет одновременный доступ из нескольких потоков, но требует соблюдения мер безопасности и наличия соответствующих условий среды выполнения.

Использование в Node.js (взаимное преобразование с Buffer)

В Node.js можно преобразовывать между бинарным типом Node.js Buffer и ArrayBuffer. Это полезно, если вы разрабатываете на TypeScript для браузеров и Node.js.

 1// Convert ArrayBuffer <-> Node.js Buffer
 2// In Node.js environment:
 3const ab = new Uint8Array([10,20,30]).buffer;
 4const nodeBuffer = Buffer.from(ab); // ArrayBuffer -> Buffer
 5console.log(nodeBuffer); // <Buffer 0a 14 1e>
 6
 7const backToAb = nodeBuffer.buffer.slice(
 8    nodeBuffer.byteOffset,
 9    nodeBuffer.byteOffset + nodeBuffer.byteLength
10);
11console.log(new Uint8Array(backToAb)); // Uint8Array [10,20,30]
  • Buffer.from(arrayBuffer) в Node.js обычно создаёт копию, но возможно совместное использование ссылок, поэтому обращайте внимание на смещения.

Особенности производительности и лучшие практики

Для оптимальной работы и эффективной обработки ArrayBuffer нужно учитывать несколько важных аспектов. Ниже приведены практические рекомендации и их объяснения.

  • Уменьшайте количество копирований При работе с большими бинарными данными используйте subarray (совместные представления) или передачу объекта, чтобы уменьшить количество копий.

  • Используйте однократное выделение больших буферов Многократное выделение небольших буферов увеличивает накладные расходы. По возможности выделяйте большой буфер сразу и используйте его части по мере необходимости.

  • Указывайте порядок байт (эндийность) явно При работе с многобайтными значениями используйте DataView и явно указывайте порядок байт. Big-endian часто используется в качестве стандарта для сетевых протоколов.

Типичные примеры использования

ArrayBuffer широко используется как в браузерах, так и в Node.js.

  1. Разбор и построение бинарных протоколов (обработка заголовков через DataView)
  2. Медиа-обработка, например, изображений и аудио-данных (fetch(...).then(res => res.arrayBuffer()))
  3. Общая память для WebAssembly (память Wasm основана на ArrayBuffer)
  4. Передача тяжёлых вычислений в Workers (передача ArrayBuffer как transferable)

Следующий код — пример получения и анализа бинарных данных.

1// Example: fetch binary data in browser and inspect first bytes
2async function fetchAndInspect(url: string) {
3  const resp = await fetch(url);
4  const ab = await resp.arrayBuffer();
5  const u8 = new Uint8Array(ab, 0, Math.min(16, ab.byteLength));
6  console.log('first bytes:', u8);
7}
  • Этот код получает бинарные данные по любому URL в браузере и отображает первые несколько байт. Код загружает данные с помощью API fetch как ArrayBuffer и анализирует первые 16 байт с помощью Uint8Array.

Резюме

ArrayBuffer — это представление сырой памяти, что позволяет эффективно работать с бинарными данными через TypedArray и DataView. Избегая ненужного копирования и явно указывая порядок байт, вы сможете реализовать безопасную и высокопроизводительную обработку бинарных данных.

Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.

YouTube Video