TypedArray в TypeScript

TypedArray в TypeScript

В этой статье объясняется TypedArray в TypeScript.

Мы объясним TypedArray в TypeScript, включая практические примеры.

YouTube Video

TypedArray в TypeScript

TypedArray — это механизм для эффективной работы с бинарными данными. Он особенно полезен для низкоуровневых бинарных операций, таких как работа с большими изображениями, сетевыми байтовыми потоками и числовыми массивами для WebGL.

Как создать ArrayBuffer

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

1// Create an ArrayBuffer of 16 bytes
2const buffer: ArrayBuffer = new ArrayBuffer(16);
3console.log("buffer.byteLength:", buffer.byteLength); // 16
  • Этот код создает пустую область памяти размером 16 байт. Сам ArrayBuffer не имеет функций для чтения/записи, поэтому доступ к нему осуществляется через TypedArray или DataView.

Типы TypedArray

Существует много типов TypedArray, например, следующие. Они различаются в зависимости от типа данных и размера, с которыми работают.

TypedArray Тип данных Размер в битах
Int8Array 8-битное целое число 8 бит
Uint8Array Беззнаковое 8-битное целое число 8 бит
Uint8ClampedArray Ограниченное беззнаковое 8-битное целое число 8 бит
Int16Array 16-битное целое число 16 бит
Uint16Array Беззнаковое 16-битное целое число 16 бит
Int32Array 32-битное целое число 32 бит
Uint32Array Беззнаковое 32-битное целое число 32 бит
Float32Array 32-битное число с плавающей запятой 32 бита
Float64Array 64-битное число с плавающей запятой 64 бита

Базовые типизированные массивы (Uint8Array, Int16Array, Float32Array и др.)

TypedArray создает 'типизированное представление' поверх ArrayBuffer. Ниже приведены примеры создания и использования типичных TypedArray.

 1// Create a buffer and different typed views over it
 2const buf = new ArrayBuffer(8); // 8 bytes
 3
 4// Create views
 5const u8 = new Uint8Array(buf);    // 8 x uint8
 6const i16 = new Int16Array(buf);   // 4 x int16 (since each int16 is 2 bytes)
 7const f32 = new Float32Array(buf); // 2 x float32 (4 bytes each)
 8
 9console.log("Uint8 length:", u8.length);
10console.log("Int16 length:", i16.length);
11console.log("Float32 length:", f32.length);
  • Создавая несколько представлений одного и того же ArrayBuffer, вы можете читать и записывать одну и ту же память с разными типами и детализацией. length представления — количество элементов, а byteLength — количество байт.

Запись и чтение (операции на уровне байтов)

Когда вы записываете значение в TypedArray, соответствующие байты в памяти обновляются. Вы можете увидеть изменения, если прочтете тот же буфер с помощью другого представления.

 1// Demonstrate writing via one view and reading via another
 2const buffer2 = new ArrayBuffer(4);
 3const u8view = new Uint8Array(buffer2);
 4const u32view = new Uint32Array(buffer2);
 5
 6u8view[0] = 0x78;
 7u8view[1] = 0x56;
 8u8view[2] = 0x34;
 9u8view[3] = 0x12;
10
11console.log("Uint8 bytes:", Array.from(u8view)); // [120, 86, 52, 18]
12console.log("Uint32 value (platform endianness):", u32view[0]); // value depends on endianness
  • В этом примере мы записываем последовательность байтов, затем читаем ту же область как 32-битное целое число. Обратите внимание, что результат зависит от порядка байт (endianness) среды выполнения.

Порядок байт (endianness) и DataView

DataView полезен, если вы хотите контролировать вопросы, связанные с порядком байт в зависимости от среды. DataView позволяет указывать порядок байт при чтении и записи.

 1// Use DataView to read/write with explicit endianness control
 2const b = new ArrayBuffer(4);
 3const dv = new DataView(b);
 4
 5// write little-endian 32-bit integer
 6dv.setUint32(0, 0x12345678, true);
 7
 8// read as little-endian and big-endian
 9const little = dv.getUint32(0, true);
10const big = dv.getUint32(0, false);
11
12console.log("little-endian read:", little.toString(16)); // "12345678"
13console.log("big-endian read:", big.toString(16));       // different value
  • DataView — это удобный механизм для детального чтения и записи байтов. Он поддерживает различные типы данных, такие как знаковые и беззнаковые числа, а также числа с плавающей запятой, и позволяет явно указывать порядок байт, что очень полезно при реализации бинарных протоколов.

Различие между subarray и slice

subarray у TypedArray возвращает представление, использующее тот же буфер, а slice возвращает новую копию. Производительность и побочные эффекты зависят от выбранного метода.

1const base = new Uint8Array([1, 2, 3, 4, 5]);
2
3const shared = base.subarray(1, 4); // shares underlying buffer
4const copied = base.slice(1, 4);    // copies data
5
6shared[0] = 99;
7console.log("base after shared modification:", base); // shows change
8console.log("copied remains:", copied);              // unaffected
  • Если вы изменяете общий просмотр, исходный массив также изменится, что может привести к непреднамеренным побочным эффектам. Если вы хотите безопасно сохранить исходный массив, вы можете заранее создать его копию с помощью slice().

Копирование буферов и преобразование типов (конвертация между TypedArray)

Мы объясним, как копировать данные между TypedArray и как вставлять части с помощью метода set.

 1// Copy and convert between typed arrays
 2const src = new Float32Array([1.5, 2.5, 3.5]);
 3const dst = new Uint16Array(src.length);
 4
 5// Convert by mapping (explicit conversion)
 6for (let i = 0; i < src.length; i++) {
 7  dst[i] = Math.round(src[i]); // simple conversion rule
 8}
 9console.log("converted dst:", Array.from(dst)); // [2, 2, 4]
10
11// Using set to copy bytes (requires compatible underlying buffer or same element type)
12const src2 = new Uint8Array([10, 20, 30]);
13const dst2 = new Uint8Array(6);
14dst2.set(src2, 2); // copy src2 into dst2 starting at index 2
15console.log("dst2 after set:", Array.from(dst2)); // [0,0,10,20,30,0]
  • Если типы элементов различаются, необходимо выполнять преобразование значений (например, округление или проверку диапазонов), а не просто копировать. Метод set позволяет быстро копировать между TypedArray с одинаковым типом элементов.

Практический пример: парсер бинарного протокола (простая реализация)

Здесь мы приводим пример простого парсера, который читает бинарные данные фиксированного формата, состоящие из 1 байта типа, 2 байт длины данных и следующей полезной нагрузки (payload).

 1// Simple binary message parser:
 2// format: [type: uint8][length: uint16 BE][payload: length bytes]
 3function parseMessages(buffer: ArrayBuffer) {
 4  const dv = new DataView(buffer);
 5  let offset = 0;
 6  const messages: { type: number; payload: Uint8Array }[] = [];
 7
 8  while (offset + 3 <= dv.byteLength) {
 9    const type = dv.getUint8(offset);
10    const length = dv.getUint16(offset + 1, false); // big-endian
11    offset += 3;
12    if (offset + length > dv.byteLength) break; // truncated
13    const payload = new Uint8Array(buffer, offset, length);
14    messages.push({ type, payload });
15    offset += length;
16  }
17
18  return messages;
19}
20
21// Example usage
22const buf = new ArrayBuffer(1 + 2 + 3 + 1 + 2 + 2); // two messages
23const dv = new DataView(buf);
24let off = 0;
25// first message: type=1, length=3, payload=[1,2,3]
26dv.setUint8(off, 1); dv.setUint16(off + 1, 3, false); off += 3;
27new Uint8Array(buf, off, 3).set([1, 2, 3]); off += 3;
28// second message: type=2, length=2, payload=[9,8]
29dv.setUint8(off, 2); dv.setUint16(off + 1, 2, false); off += 3;
30new Uint8Array(buf, off, 2).set([9, 8]);
31
32console.log(parseMessages(buf));
  • В этом примере заголовок считывается с помощью DataView, а срез полезной нагрузки создается с помощью Uint8Array. Порядок байт и проверка длины буфера имеют важное значение.

Web API и TypedArray (пример: получение бинарных данных)

Это типичный пример обработки ArrayBuffer, полученного из сетевого запроса с использованием TypedArray.

 1// Example of fetching binary and mapping to typed array
 2async function fetchBinary(url: string) {
 3  const res = await fetch(url);
 4  const ab = await res.arrayBuffer();
 5  const view = new Uint8Array(ab);
 6  // process view...
 7  console.log("received bytes:", view.length);
 8  return view;
 9}
10
11// Usage (call in async context)
12// fetchBinary("/path/to/file.bin");
  • Вы можете напрямую передать ArrayBuffer, полученный с помощью Response.arrayBuffer(), в TypedArray. Это используется для изображений, аудио или собственных бинарных протоколов.

Советы по производительности и распространённые ошибки

Вот несколько советов по производительности и распространённых ошибок, которые полезно знать при работе с TypedArray:.

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

  • Будьте внимательны с порядком байт В сетевых взаимодействиях или файловых форматах порядок байт (byte order) часто явно указывается. Использование DataView позволяет явно задавать порядок байт при чтении и записи, предотвращая неправильную интерпретацию данных.

  • Учитывайте диапазоны значений каждого типа Например, Uint8 может представлять только значения от 0 до 255. Если вы вводите отрицательное значение, может произойти усечение или переполнение значения, поэтому следует определить правила преобразования при необходимости.

  • Учитывайте нагрузку на сборку мусора Частое создание крупных объектов ArrayBuffer увеличивает нагрузку на управление памятью. В ситуациях, критичных к производительности, стоит рассмотреть возможность проектирования кода для максимального повторного использования существующих буферов.

Резюме

TypedArray — это механизм для быстрой и эффективной работы с бинарными данными. Комбинируя ArrayBuffer, который выделяет область байт фиксированной длины, с TypedArray или DataView, которые читают и записывают данные с использованием определённых типов, вы можете выполнять гибкие бинарные операции. В зависимости от вашего случая использования вы можете выбрать между использованием subarray/slice или DataView, а также проектировать свою реализацию с учетом порядка байтов и копирования.

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

YouTube Video