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 ]
Uint8Arrayto widok tablicy bajtów, który można obsługiwać jak zwykłą tablicę. Jeśli umieścisz kilka widoków na tym samymArrayBuffer, 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"
DataViewumoż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.encodezwracaUint8Array, więc jeśli potrzebujeszArrayBuffer, powinieneś odwołać się do jego właściwości.buffer. Za pomocą metodyslicemoż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
slicezwraca nowyArrayBuffer, jeśli zależy Ci na wydajności lub współdzieleniu referencji, możesz użyć metodysubarrayzTypedArray, 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 jakArrayBuffer, 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
SharedArrayBufferumoż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
DataViewi 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.
- Parsowanie i tworzenie protokołów binarnych (przetwarzanie nagłówków za pomocą
DataView) - Przetwarzanie mediów, takich jak dane obrazów i dźwięku (
fetch(...).then(res => res.arrayBuffer())) - Wspólna pamięć dla WebAssembly (pamięć Wasm oparta jest na
ArrayBuffer) - Przenoszenie ciężkich zadań obliczeniowych do Workerów (przekazywanie
ArrayBufferjako 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
fetchjakoArrayBufferi 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.