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.
- Разбор и построение бинарных протоколов (обработка заголовков через
DataView) - Медиа-обработка, например, изображений и аудио-данных (
fetch(...).then(res => res.arrayBuffer())) - Общая память для WebAssembly (память Wasm основана на
ArrayBuffer) - Передача тяжёлых вычислений в 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-канал.