TypedArray w TypeScript
Ten artykuł wyjaśnia TypedArray w TypeScript.
Wyjaśnimy TypedArray w TypeScript, w tym praktyczne przykłady.
YouTube Video
TypedArray w TypeScript
TypedArray to mechanizm do wydajnego przetwarzania danych binarnych. Jest szczególnie przydatny do niskopoziomowych operacji binarnych, takich jak duże dane obrazów, strumienie bajtów sieciowych oraz tablice liczbowe dla WebGL.
Jak utworzyć ArrayBuffer
ArrayBuffer reprezentuje obszar bajtów o stałej długości. Najpierw utwórz bufor i sprawdź jego rozmiar oraz długość w bajtach.
1// Create an ArrayBuffer of 16 bytes
2const buffer: ArrayBuffer = new ArrayBuffer(16);
3console.log("buffer.byteLength:", buffer.byteLength); // 16
- Ten kod tworzy pusty obszar o długości 16 bajtów. Sam
ArrayBuffernie posiada funkcji odczytu/zapisu, więc dostęp do niego uzyskujesz przezTypedArrayalboDataView.
Typy TypedArray
Istnieje wiele typów TypedArray, takich jak poniższe. Różnią się w zależności od typu danych i ich rozmiaru, które obsługują.
| TypedArray | Typ danych | Rozmiar bitowy |
|---|---|---|
Int8Array |
Liczba całkowita 8-bitowa | 8 bitów |
Uint8Array |
Nieznakowana liczba całkowita 8-bitowa | 8 bitów |
Uint8ClampedArray |
Ściśnięta nieznakowana liczba całkowita 8-bitowa | 8 bitów |
Int16Array |
Liczba całkowita 16-bitowa | 16 bitów |
Uint16Array |
Nieznakowana liczba całkowita 16-bitowa | 16 bitów |
Int32Array |
Liczba całkowita 32-bitowa | 32 bitów |
Uint32Array |
Nieznakowana liczba całkowita 32-bitowa | 32 bitów |
Float32Array |
32-bitowa liczba zmiennoprzecinkowa | 32 bity |
Float64Array |
64-bitowa liczba zmiennoprzecinkowa | 64 bity |
Podstawowe TypedArray (Uint8Array, Int16Array, Float32Array itd.)
TypedArray tworzy 'widok typowany' na istniejącym ArrayBuffer. Poniżej znajdują się przykłady tworzenia i używania kilku typowych 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);- Tworząc wiele widoków na tym samym
ArrayBuffer, możesz czytać i zapisywać tę samą pamięć w różnych typach lub z różną szczegółowością.lengthwidoku to liczba elementów, abyteLengthto liczba bajtów.
Zapis i odczyt (operacje na poziomie bajtów)
Gdy zapisujesz wartość do TypedArray, odpowiednie bajty w pamięci są aktualizowane. Zmiany można zobaczyć, gdy ten sam bufor przeczytasz za pomocą innego widoku.
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
- W tym przykładzie zapisujemy sekwencję bajtów i odczytujemy ten sam obszar jako 32-bitową liczbę całkowitą. Zwróć uwagę, że wynik zależy od porządku bajtów (endianness) środowiska uruchomieniowego.
Porządek bajtów (endianness) i DataView
DataView jest przydatny, gdy chcesz kontrolować kwestie porządku bajtów zależnego od środowiska. DataView pozwala określać porządek bajtów przy odczycie i zapisie.
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
DataViewto wygodny mechanizm do szczegółowego odczytu i zapisu bajtów. Obsługuje różne typy, takie jak liczby całkowite ze znakiem lub bez znaku oraz liczby zmiennoprzecinkowe, pozwalając jawnie określić porządek bajtów (endianness), co jest bardzo przydatne przy implementowaniu protokołów binarnych.
Różnica między subarray i slice
subarray w TypedArray zwraca widok dzielący oryginalny bufor, natomiast slice zwraca nową kopię. Wydajność i skutki uboczne różnią się w zależności od użytej metody.
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
- Jeśli zmodyfikujesz współdzielony widok, oryginalna tablica również się zmieni, co może powodować niezamierzone skutki uboczne. Jeśli chcesz bezpiecznie zachować oryginalną tablicę, możesz wcześniej utworzyć jej kopię za pomocą
slice().
Kopiowanie buforów i konwersja typów (konwersja między TypedArray)
Wyjaśnimy, jak kopiować dane między TypedArray i jak wklejać fragmenty za pomocą metody 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]
- Jeśli typy elementów się różnią, trzeba przekonwertować wartości, np. zaokrąglając je lub sprawdzając zakres, zamiast po prostu kopiować.
setumożliwia szybkie kopiowanie międzyTypedArraytego samego typu elementów.
Praktyczny przykład: parser protokołu binarnego (prosta implementacja)
Tutaj przedstawiamy prosty przykład parsera, który odczytuje dane binarne o ustalonym formacie: 1 bajt typu, 2 bajty długości danych i następujący po nich 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));- W tym przykładzie nagłówek jest odczytywany za pomocą
DataView, a fragment danych (payload) tworzony przy użyciuUint8Array. Porządek bajtów (endianness) i kontrola długości bufora są istotne.
Web API i TypedArray (Przykład: pobieranie danych binarnych)
To typowy przykład obsługi ArrayBuffer uzyskanego z żądania sieciowego za pomocą 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");
ArrayBufferuzyskany za pomocąResponse.arrayBuffer()możesz bezpośrednio przekazać doTypedArray. Jest to używane do obrazów, audio lub niestandardowych protokołów binarnych.
Wskazówki dotyczące wydajności i częste pułapki
Oto kilka 'wskazówek dotyczących wydajności' i 'częstych pułapek', które warto znać przy używaniu TypedArray:.
-
Unikaj niepotrzebnego kopiowania Aby wydajnie przetwarzać duże dane, możesz ograniczyć niepotrzebne kopiowanie, tworząc częściowe widoki za pomocą
subarraylub współdzieląc ten samArrayBufferpomiędzy różne widoki. -
Uważaj na porządek bajtów (endianness) W komunikacji sieciowej czy formatach plików często określony jest porządek danych (porządek bajtów). Użycie
DataViewpozwala jawnie określić porządek bajtów przy odczycie i zapisie, co zapobiega błędnej interpretacji danych. -
Zwracaj uwagę na zakresy wartości dla każdego typu Na przykład,
Uint8może reprezentować tylko wartości od 0 do 255. Jeśli wprowadzisz wartość ujemną, może dojść do obcięcia lub nadpisania wartości, dlatego powinieneś zdefiniować odpowiednie zasady konwersji. -
Zwróć uwagę na wpływ na garbage collection (GC) Częste ponowne tworzenie dużych
ArrayBufferzwiększa obciążenie zarządzania pamięcią. W sytuacjach krytycznych pod względem wydajności warto rozważyć projektowanie kodu w taki sposób, aby jak najczęściej wykorzystywać istniejące bufory.
Podsumowanie
TypedArray to mechanizm do szybkiego i wydajnego przetwarzania danych binarnych. Łącząc ArrayBuffer, który rezerwuje obszar bajtów o stałej długości, z TypedArray lub DataView, które odczytują i zapisują zawartość w określonych typach, możesz wykonywać elastyczne operacje binarne. W zależności od przypadku użycia możesz wybrać pomiędzy subarray/slice a DataView oraz zaprojektować implementację, zwracając uwagę na kolejność bajtów (endianness) i kopiowanie.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.