ArrayBuffer en TypeScript

ArrayBuffer en TypeScript

Este artículo explica ArrayBuffer en TypeScript.

Explicaremos ArrayBuffer en TypeScript paso a paso, cubriendo desde fundamentos hasta técnicas prácticas.

YouTube Video

ArrayBuffer en TypeScript

ArrayBuffer es un objeto incorporado que representa una “zona de memoria en bruto” para datos binarios. Representa un búfer en bruto de longitud fija, sobre el que se superponen TypedArray o DataView para lectura y escritura.

Conceptos básicos y características de ArrayBuffer

ArrayBuffer es una secuencia de bytes de longitud fija. Se especifica el tamaño en bytes al crearlo, y su longitud no puede cambiarse posteriormente.

1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
  • Este código crea un búfer vacío de 16 bytes. Puede comprobar el tamaño usando byteLength.

TypedArray y DataView — Vistas para manipular buffers

ArrayBuffer no tiene la capacidad de leer o escribir datos. Por lo tanto, las operaciones reales se realizan a través de TypedArray o DataView, especificando tipos como enteros o números de punto flotante y la endianness al acceder a los datos.

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 es una vista de matriz a nivel de byte y puede accederse como a un array normal. Si coloca varias vistas sobre el mismo ArrayBuffer, comparten la misma memoria para leer y escribir.

DataView: Lectura y escritura en límites arbitrarios y endianness

DataView es útil para leer y escribir a nivel de byte con precisión, permitiéndole especificar little o 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 te permite leer y escribir valores especificando desplazamientos en la memoria, lo que la hace adecuada para implementar protocolos que requieren manejar el orden de bytes de red.

Conversión entre cadenas de texto y ArrayBuffer (TextEncoder / TextDecoder)

Para convertir texto a binario y viceversa, utiliza TextEncoder y 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 devuelve un Uint8Array, así que si necesitas un ArrayBuffer, debes referenciar su propiedad .buffer. Con el método slice, puedes extraer los datos requeridos especificando el desplazamiento y la longitud.

Cortar y copiar ArrayBuffer

El método slice devuelve un nuevo ArrayBuffer. ArrayBuffer no puede cambiar de tamaño; si es necesario cambiar el tamaño, crea un nuevo buffer y copia los datos en él.

 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
  • Como el método slice devuelve un nuevo ArrayBuffer, si priorizas la eficiencia o compartir referencias, puedes usar el método subarray de TypedArray para referirte al mismo buffer.

Diferencias entre TypedArray.subarray y copiar datos

subarray de TypedArray devuelve una nueva vista que hace referencia al mismo 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]
  • Las vistas compartidas pueden evitar el coste de la copia, pero como se refieren al mismo buffer, es necesario tener cuidado con los efectos secundarios.

Concatenar búferes (combinar varios ArrayBuffers)

Como ArrayBuffer tiene longitud fija, para combinar varios búferes cree un nuevo ArrayBuffer y copie los datos.

 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]
  • Si concatenas grandes cantidades de datos con frecuencia, precomputar el tamaño total y asignar un nuevo buffer solo una vez, como en este código, es más eficiente.

Pasar un ArrayBuffer a un Worker (Transferible)

En el postMessage del navegador, puedes transferir un ArrayBuffer como un objeto "transferible". La propiedad puede transferirse sin copia, lo que evita el coste de la copia.

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
  • Al especificar los objetos a transferir en un arreglo como segundo argumento de postMessage, puedes transferir la propiedad, sin copia, de objetos transferibles como ArrayBuffer.
  • Después de la transferencia, el buffer queda "separado" del lado original y no se puede acceder a él. El uso de SharedArrayBuffer permite el acceso simultáneo desde múltiples hilos, pero su uso conlleva requisitos de seguridad y restricciones del entorno.

Manejo en Node.js (interconversión con Buffer)

En Node.js puede convertir entre el tipo binario Buffer y ArrayBuffer. Esto es útil al apuntar tanto a navegadores como a Node.js con TypeScript.

 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) en Node.js usualmente crea una copia, pero existen casos donde es posible compartir la referencia, así que preste atención a los offsets.

Consideraciones de rendimiento y mejores prácticas

Para optimizar el rendimiento y manejar ArrayBuffer eficientemente, hay varios puntos importantes a tener en cuenta. A continuación, enumeramos y explicamos mejores prácticas aplicables.

  • Reducir la cantidad de copias Al manejar binarios grandes, use subarray (vista compartida) o objetos transferibles para reducir copias.

  • Asignar grandes búferes de una vez Asignar pequeños búferes repetidamente aumenta la sobrecarga. Si es posible, asigne un búfer grande de una vez y utilice partes de él según sea necesario.

  • Especifique la endianidad explícitamente Al manejar valores multibyte, use DataView y especifique explícitamente el endianness. Big-endian es a menudo el estándar para protocolos de red.

Ejemplos de casos de uso comunes

ArrayBuffer se usa ampliamente tanto en navegadores como en Node.js.

  1. Parseo y construcción de protocolos binarios (procesando información de cabecera con DataView)
  2. Procesamiento de medios como datos de imagen y audio (fetch(...).then(res => res.arrayBuffer()))
  3. Memoria compartida para WebAssembly (la memoria de Wasm funciona sobre ArrayBuffer)
  4. Derivar procesamiento pesado a Workers (pasando ArrayBuffer como transferible)

El siguiente código es un ejemplo de adquisición y análisis de datos binarios.

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}
  • Este código obtiene datos binarios de cualquier URL en el navegador y muestra los primeros bytes. El código carga datos mediante la API fetch como un ArrayBuffer e inspecciona los primeros 16 bytes con un Uint8Array.

Resumen

ArrayBuffer es una representación de memoria en bruto, permitiendo operaciones binarias eficientes mediante TypedArray y DataView. Al diseñar para evitar copias innecesarias y especificar explícitamente el endianness, puede lograr un procesamiento binario seguro y eficiente.

Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.

YouTube Video