ArrayBuffer w TypeScript

ArrayBuffer w TypeScript

Ten artykuł wyjaśnia, czym jest ArrayBuffer w TypeScript.

Wyjaśnimy ArrayBuffer w TypeScript krok po kroku, od podstaw po praktyczne techniki.

YouTube Video

ArrayBuffer w TypeScript

ArrayBuffer to wbudowany obiekt, który reprezentuje „surowy obszar pamięci” na dane binarne. Reprezentuje bufor o stałej długości, na którym można nakładać TypedArray lub DataView w celu odczytu i zapisu danych.

Podstawowe pojęcia i cechy ArrayBuffer

ArrayBuffer to sekwencja bajtów o stałej długości. Podczas tworzenia określasz rozmiar w bajtach, a jego długość nie może być później zmieniona.

1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
  • Ten kod tworzy pusty bufor o wielkości 16 bajtów. Możesz sprawdzić rozmiar za pomocą właściwości byteLength.

TypedArray i DataView — widoki do manipulacji buforami

ArrayBuffer nie posiada możliwości odczytu ani zapisu danych. Dlatego rzeczywiste operacje są wykonywane za pomocą TypedArray lub DataView, określając typy takie jak liczby całkowite czy zmiennoprzecinkowe oraz kolejność bajtów podczas uzyskiwania dostępu do danych.

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 to widok tablicy bajtów, który można obsługiwać jak zwykłą tablicę. Jeśli umieścisz kilka widoków na tym samym ArrayBuffer, będą one dzielić tę samą pamięć do odczytu i zapisu.

DataView: odczyt i zapis na dowolnych granicach oraz z wybraną kolejnością bajtów (endianness)

DataView jest przydatny do precyzyjnego odczytu i zapisu bajtów, umożliwiając określenie little-endian lub 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 umożliwia odczytywanie i zapisywanie wartości przy określeniu przesunięć w pamięci, co sprawia, że nadaje się do implementacji protokołów wymagających obsługi kolejności bajtów sieciowych.

Konwersja między tekstem a ArrayBuffer (TextEncoder / TextDecoder)

Aby przekształcić tekst do postaci binarnej i odwrotnie, użyj TextEncoder i 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 zwraca Uint8Array, więc jeśli potrzebujesz ArrayBuffer, powinieneś odwołać się do jego właściwości .buffer. Za pomocą metody slice możesz wyodrębnić potrzebne dane, określając offset i długość.

Dzielenie i kopiowanie ArrayBuffer

Metoda slice zwraca nowy ArrayBuffer. ArrayBuffer nie można zmieniać rozmiaru; jeśli to konieczne, utwórz nowy bufor i skopiuj do niego dane.

 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
  • Ponieważ metoda slice zwraca nowy ArrayBuffer, jeśli zależy Ci na wydajności lub współdzieleniu referencji, możesz użyć metody subarray z TypedArray, aby odwoływać się do tego samego bufora.

Różnica między TypedArray.subarray a kopiowaniem

subarray z TypedArray zwraca nowy widok, który odnosi się do tego samego 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]
  • Współdzielone widoki mogą uniknąć kosztów kopiowania, ale ponieważ odnoszą się do tego samego bufora, trzeba uważać na skutki uboczne.

Łączenie buforów (łączenie wielu ArrayBuffer)

Ponieważ ArrayBuffer ma stałą długość, aby połączyć kilka buforów, należy utworzyć nowy ArrayBuffer i skopiować dane.

 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]
  • Jeśli często łączysz duże ilości danych, wcześniejsze obliczenie całkowitego rozmiaru i przydzielenie nowego bufora tylko raz — jak w tym kodzie — jest bardziej wydajne.

Przekazywanie ArrayBuffer do Workerów (Transferable)

W postMessage w przeglądarce możesz przekazać ArrayBuffer jako obiekt „transferable”. Własność może zostać przeniesiona bez kopiowania, co pozwala uniknąć związanych z tym kosztów.

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
  • Określając obiekty do przekazania w tablicy jako drugi argument postMessage, możesz przenieść własność obiektów transferowalnych, takich jak ArrayBuffer, bez kopiowania.
  • Po przekazaniu bufor staje się „odłączony” po stronie źródłowej i nie można już uzyskać do niego dostępu. Używanie SharedArrayBuffer umożliwia jednoczesny dostęp z wielu wątków, ale wymaga spełnienia wymogów bezpieczeństwa i ograniczeń środowiska.

Obsługa w Node.js (konwersja z i do Buffer)

W Node.js możesz konwertować pomiędzy typem binarnym Buffer a ArrayBuffer. Jest to przydatne, jeśli zamierzasz obsługiwać zarówno przeglądarkę, jak i Node.js w 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) w Node.js zwykle tworzy kopię, ale w niektórych przypadkach możliwe jest współdzielenie referencji — zwróć uwagę na przesunięcia.

Wskazówki dotyczące wydajności i najlepsze praktyki

Aby zoptymalizować wydajność i efektywnie obsługiwać ArrayBuffer, należy pamiętać o kilku ważnych kwestiach. Poniżej wymieniamy i wyjaśniamy praktyczne dobre praktyki.

  • Ogranicz liczbę kopiowań Podczas pracy z dużymi danymi binarnymi używaj subarray (współdzielonego widoku) lub transferables, aby zmniejszyć liczbę kopii.

  • Przydzielaj duże bufory jednorazowo Wielokrotne przydzielanie małych buforów zwiększa narzut. Jeśli to możliwe, przydziel od razu duży bufor i używaj jego części w razie potrzeby.

  • Wyraźnie określ endianowość Podczas obsługi wartości wielobajtowych używaj DataView i jawnie określaj endianness. Big-endian jest często standardem w protokołach sieciowych.

Typowe przykłady użycia

ArrayBuffer jest szeroko używany zarówno w przeglądarkach, jak i w Node.js.

  1. Parsowanie i tworzenie protokołów binarnych (przetwarzanie nagłówków za pomocą DataView)
  2. Przetwarzanie mediów, takich jak dane obrazów i dźwięku (fetch(...).then(res => res.arrayBuffer()))
  3. Wspólna pamięć dla WebAssembly (pamięć Wasm oparta jest na ArrayBuffer)
  4. Przenoszenie ciężkich zadań obliczeniowych do Workerów (przekazywanie ArrayBuffer jako transferable)

Poniższy kod to przykład pobierania i analizy danych binarnych.

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}
  • Ten kod pobiera dane binarne z dowolnego adresu URL w przeglądarce i wyświetla pierwsze kilka bajtów. Kod ładuje dane pobrane przez API fetch jako ArrayBuffer i bada pierwsze 16 bajtów za pomocą Uint8Array.

Podsumowanie

ArrayBuffer to reprezentacja surowej pamięci, umożliwiająca wydajne operacje binarne poprzez TypedArray i DataView. Projektując aplikację tak, by unikać niepotrzebnego kopiowania i jawnie wskazując endianness, możesz zapewnić bezpieczne i wydajne przetwarzanie danych binarnych.

Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.

YouTube Video