JavaScript Typed Arrays
JavaScript Typed Arrays sind array-ähnliche Objekte, die einen Mechanismus zum Lesen und Schreiben von rohen Binärdaten in Speicherpuffern bereitstellen.
Typed Arrays sind nicht dazu gedacht, Arrays für jegliche Art von Funktionalität zu ersetzen. Stattdessen bieten sie Entwicklern eine vertraute Schnittstelle zur Manipulation von Binärdaten. Dies ist nützlich beim Interagieren mit Plattformfunktionen, wie etwa bei der Audio- und Videobearbeitung, dem Zugriff auf Rohdaten über WebSockets und so weiter. Jedes Element in einem JavaScript Typed Array ist ein roher Binärwert in einem von mehreren unterstützten Formaten, von 8-Bit-Ganzzahlen bis zu 64-Bit-Floatzahlen.
Typed Array Objekte teilen viele der gleichen Methoden mit Arrays, jedoch mit ähnlicher Semantik. Allerdings sind Typed Arrays nicht mit normalen Arrays zu verwechseln, da der Aufruf von Array.isArray()
auf einem Typed Array false
zurückgibt. Darüber hinaus werden nicht alle Methoden, die für normale Arrays verfügbar sind, von Typed Arrays unterstützt (z.B. push und pop).
Um maximale Flexibilität und Effizienz zu erreichen, teilen JavaScript Typed Arrays die Implementierung in Buffer und Views auf. Ein Buffer ist ein Objekt, das ein Stück Daten darstellt; es hat kein Format, von dem man sprechen könnte, und bietet keinen Mechanismus zum Zugriff auf seinen Inhalt. Um auf den im Buffer enthaltenen Speicher zuzugreifen, müssen Sie eine View verwenden. Eine View bietet einen Kontext — das heißt, einen Datentyp, einen Startversatz und eine Anzahl von Elementen.
Buffer
Es gibt zwei Arten von Buffern: ArrayBuffer
und SharedArrayBuffer
. Beide sind Low-Level-Darstellungen eines Speicherbereichs. Sie haben "Array" in ihren Namen, aber sie haben nicht viel mit Arrays zu tun – Sie können nicht direkt auf sie lesen oder schreiben. Stattdessen sind Buffer generische Objekte, die nur Rohdaten enthalten. Um auf den Speicher zuzugreifen, der von einem Buffer dargestellt wird, müssen Sie eine View verwenden.
Buffer unterstützen die folgenden Aktionen:
- Zuordnen: Sobald ein neuer Buffer erstellt wird, wird ein neuer Speicherbereich zugewiesen und auf
0
initialisiert. - Kopieren: Mit der Methode
slice()
können Sie einen Teil des Speichers effizient kopieren, ohne Views zu erstellen, um jedes Byte manuell zu kopieren. - Transferieren: Mithilfe der Methoden
transfer()
undtransferToFixedLength()
können Sie den Besitz des Speicherbereichs an ein neues Buffer-Objekt übertragen. Dies ist nützlich, wenn Daten zwischen verschiedenen Ausführungskontexten ohne Kopieren übertragen werden müssen. Nach der Übertragung ist der ursprüngliche Buffer nicht mehr verwendbar. EinSharedArrayBuffer
kann nicht übertragen werden (da der Buffer bereits von allen Ausführungskontexten geteilt wird). - Größenänderung: Mit der Methode
resize()
können Sie den Speicherbereich vergrößern (mehr Speicherplatz beanspruchen, solange er das voreingestelltemaxByteLength
Limit nicht überschreitet, oder etwas Speicherplatz freigeben).SharedArrayBuffer
kann nur vergrößert werden, aber nicht verkleinert.
Der Unterschied zwischen ArrayBuffer
und SharedArrayBuffer
besteht darin, dass ersterer immer nur von einem einzelnen Ausführungskontext zu einer Zeit besessen wird. Wenn Sie einen ArrayBuffer
an einen anderen Ausführungskontext übergeben, wird er übertragen und der ursprüngliche ArrayBuffer
wird unbrauchbar. Dies stellt sicher, dass nur ein Ausführungskontext gleichzeitig auf den Speicher zugreifen kann. Ein SharedArrayBuffer
wird nicht übertragen, wenn er an einen anderen Ausführungskontext übergeben wird, sodass er von mehreren Ausführungskontexten gleichzeitig zugegriffen werden kann. Dies kann zum Auftreten von Race-Bedingungen führen, wenn mehrere Threads auf denselben Speicherbereich zugreifen, weshalb Operationen wie die Atomics
Methoden nützlich werden.
Views
Derzeit gibt es zwei Hauptarten von Views: Typed Array Views und DataView
. Typed Arrays bieten Utility-Methoden, mit denen Sie Binärdaten bequem transformieren können. DataView
ist eher Low-Level und erlaubt eine granulare Kontrolle darüber, wie Daten zugegriffen werden. Die Möglichkeiten, Daten mithilfe der beiden Views zu lesen und zu schreiben, sind sehr unterschiedlich.
Beide Arten von Views führen dazu, dass ArrayBuffer.isView()
true
zurückgibt. Sie haben beide die folgenden Eigenschaften:
buffer
-
Der zugrunde liegende Buffer, den die View referenziert.
byteOffset
-
Der Versatz, in Bytes, der View vom Anfang ihres Buffers aus.
byteLength
-
Die Länge, in Bytes, der View.
Beide Konstruktoren akzeptieren die oben genannten drei als separate Argumente, obwohl die Konstruktoren von Typed Arrays length
als die Anzahl der Elemente statt der Anzahl der Bytes akzeptieren.
Typed Array Views
Typed Array Views haben selbsterklärende Namen und bieten Views für alle üblichen numerischen Typen wie Int8
, Uint32
, Float64
und so weiter. Es gibt eine spezielle Typed Array View, Uint8ClampedArray
, die die Werte zwischen 0
und 255
begrenzt. Dies ist nützlich für die Canvas-Datenverarbeitung, zum Beispiel.
Typ | Wertebereich | Größe in Bytes | Web IDL Typ |
---|---|---|---|
Int8Array |
-128 bis 127 | 1 | byte |
Uint8Array |
0 bis 255 | 1 | octet |
Uint8ClampedArray |
0 bis 255 | 1 | octet |
Int16Array |
-32768 bis 32767 | 2 | short |
Uint16Array |
0 bis 65535 | 2 | unsigned short |
Int32Array |
-2147483648 bis 2147483647 | 4 | long |
Uint32Array |
0 bis 4294967295 | 4 | unsigned long |
Float16Array |
-65504 bis 65504 |
2 | N/A |
Float32Array |
-3.4e38 bis 3.4e38 |
4 | unrestricted float |
Float64Array |
-1.8e308 bis 1.8e308 |
8 | unrestricted double |
BigInt64Array |
-263 bis 263 - 1 | 8 | bigint |
BigUint64Array |
0 bis 264 - 1 | 8 | bigint |
Alle Typed Array Views haben dieselben Methoden und Eigenschaften, wie von der TypedArray
Klasse definiert. Sie unterscheiden sich nur im zugrunde liegenden Datentyp und der Größe in Bytes. Dies wird ausführlicher im Abschnitt Wertekodierung und Normalisierung behandelt.
Typed Arrays sind im Prinzip feststehend, daher sind Array-Methoden, die die Länge eines Arrays ändern könnten, nicht verfügbar. Dazu gehören pop
, push
, shift
, splice
und unshift
. Darüber hinaus ist flat
nicht verfügbar, da es keine verschachtelten Typed Arrays gibt, und verwandte Methoden wie concat
und flatMap
haben keine großen Anwendungsfälle, daher sind sie nicht verfügbar. Da splice
nicht verfügbar ist, ist auch toSpliced
nicht vorhanden. Alle anderen Array-Methoden werden zwischen Array
und TypedArray
geteilt.
Andererseits hat TypedArray
die zusätzlichen set
und subarray
Methoden, die die Arbeit mit mehreren Typed Arrays, die denselben Buffer anzeigen, optimieren. Die set()
Methode ermöglicht das Setzen mehrerer Typed Array Indizes auf einmal unter Verwendung von Daten aus einem anderen Array oder Typed Array. Wenn die beiden Typed Arrays denselben zugrunde liegenden Buffer teilen, kann der Vorgang effizienter sein, da es sich um eine schnelle Speicherbewegung handelt. Die subarray()
Methode erstellt eine neue Typed Array View, die denselben Buffer wie das ursprüngliche Typed Array referenziert, jedoch mit einem engeren Bereich.
Es gibt keine Möglichkeit, die Länge eines Typed Arrays direkt zu ändern, ohne den zugrunde liegenden Buffer zu ändern. Wenn jedoch die Typed Array View einen größenveränderbaren Buffer anzeigt und keine feste byteLength
hat, ist sie längentrackend und wird automatisch an den zugrunde liegenden Buffer angepasst, sobald der größenveränderbare Buffer geändert wird. Siehe Verhalten beim Anzeigen eines größenveränderbaren Buffers für Einzelheiten.
Ähnlich wie bei regulären Arrays können Sie auf Elemente im Typed Array mit Klammernotation zugreifen. Die entsprechenden Bytes im zugrunde liegenden Buffer werden abgerufen und als Zahl interpretiert. Jeder Zugriff auf Eigenschaften mit einer Zahl (oder der stringierten Darstellung einer Zahl, da Zahlen beim Zugriff auf Eigenschaften immer in Strings umgewandelt werden) wird vom Typed Array weitergeleitet – sie interagieren nie mit dem Objekt selbst. Das bedeutet zum Beispiel:
- Der Zugriff auf einen Index außerhalb des zulässigen Bereichs gibt immer
undefined
zurück, ohne tatsächlich auf die Eigenschaft im Objekt zuzugreifen. - Jeder Versuch, auf eine solche Eigenschaft außerhalb des zulässigen Bereichs zu schreiben, hat keine Wirkung: Es wird kein Fehler ausgelöst, aber der Buffer oder das Typed Array wird auch nicht verändert.
- Typed Array Indizes scheinen konfigurierbar und beschreibbar zu sein, aber jeder Versuch, ihre Attribute zu ändern, schlägt fehl.
const uint8 = new Uint8Array([1, 2, 3]);
console.log(uint8[0]); // 1
// For illustrative purposes only. Not for production code.
uint8[-1] = 0;
uint8[2.5] = 0;
uint8[NaN] = 0;
console.log(Object.keys(uint8)); // ["0", "1", "2"]
console.log(uint8[NaN]); // undefined
// Non-numeric access still works
uint8[true] = 0;
console.log(uint8[true]); // 0
Object.freeze(uint8); // TypeError: Cannot freeze array buffer views with elements
DataView
Der DataView
ist eine low-level Schnittstelle, die eine hilfreiche Schnittstellenschnittstelle zum Lesen und Schreiben von Daten eines beliebigen Formats in den Buffer bietet. Dies ist nützlich, wenn unterschiedliche Datentypen behandelt werden müssen, zum Beispiel. Typed Array Views sind in der nativen Byte-Reihenfolge (siehe Endianness) Ihrer Plattform. Mit einem DataView
kann die Byte-Reihenfolge gesteuert werden. Standardmäßig ist sie big-endian – die Bytes sind von den bedeutendsten zu den wenig bedeutenden geordnet. Dies kann umgekehrt werden, indem die Bytes von den wenig bedeutenden zu den bedeutendsten geordnet werden (little-endian) mithilfe von Getter/Setter-Methoden.
DataView
erfordert keine Ausrichtung; Mehr-Byte-Lesen und -Schreiben kann an jedem angegebenen Offset gestartet werden. Die Setter-Methoden funktionieren auf dieselbe Weise.
Das folgende Beispiel verwendet einen DataView
, um die binäre Darstellung einer beliebigen Zahl zu erhalten:
function toBinary(
x,
{ type = "Float64", littleEndian = false, separator = " ", radix = 16 } = {},
) {
const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT;
const dv = new DataView(new ArrayBuffer(bytesNeeded));
dv[`set${type}`](0, x, littleEndian);
const bytes = Array.from({ length: bytesNeeded }, (_, i) =>
dv
.getUint8(i)
.toString(radix)
.padStart(8 / Math.log2(radix), "0"),
);
return bytes.join(separator);
}
console.log(toBinary(1.1)); // 3f f1 99 99 99 99 99 9a
console.log(toBinary(1.1, { littleEndian: true })); // 9a 99 99 99 99 99 f1 3f
console.log(toBinary(20, { type: "Int8", radix: 2 })); // 00010100
Web-APIs, die Typed Arrays verwenden
Hier sind einige Beispiele für APIs, die Typed Arrays verwenden; es gibt weitere, und ständig kommen mehr hinzu.
FileReader.prototype.readAsArrayBuffer()
-
Die
FileReader.prototype.readAsArrayBuffer()
Methode beginnt mit dem Lesen des Inhalts des angegebenenBlob
oderFile
. fetch()
-
Die
body
Option zufetch()
kann ein Typed Array oderArrayBuffer
sein, sodass Sie diese Objekte als Payload einerPOST
-Anfrage senden können. ImageData.data
-
Ist ein
Uint8ClampedArray
, das ein eindimensionales Array darstellt, das die Daten in der RGBA-Reihenfolge enthält, mit ganzzahligen Werten zwischen0
und255
inklusive.
Beispiele
Verwendung von Views mit Buffern
Zuerst müssen wir einen Buffer erstellen, hier mit einer festen Länge von 16 Bytes:
const buffer = new ArrayBuffer(16);
Zu diesem Zeitpunkt haben wir ein Stück Speicher, dessen Bytes alle auf 0 vorinitialisiert sind. Allerdings können wir damit nicht viel anfangen. Zum Beispiel können wir bestätigen, dass der Buffer die richtige Größe hat:
if (buffer.byteLength === 16) {
console.log("Yes, it's 16 bytes.");
} else {
console.log("Oh no, it's the wrong size!");
}
Bevor wir wirklich mit diesem Buffer arbeiten können, müssen wir eine View erstellen. Lassen Sie uns eine View erstellen, die die Daten im Buffer als ein Array von 32-Bit-Ganzzahlen behandelt:
const int32View = new Int32Array(buffer);
Jetzt können wir die Felder im Array wie ein normales Array zugreifen:
for (let i = 0; i < int32View.length; i++) {
int32View[i] = i * 2;
}
Dies füllt die 4 Einträge im Array aus (4 Einträge zu je 4 Bytes ergeben insgesamt 16 Bytes) mit den Werten 0
, 2
, 4
und 6
.
Mehrere Views auf dieselben Daten
Besonders interessant wird es, wenn man bedenkt, dass man mehrere Views auf dieselben Daten erstellen kann. Zum Beispiel, basierend auf dem obigen Code, können wir so fortfahren:
const int16View = new Int16Array(buffer);
for (let i = 0; i < int16View.length; i++) {
console.log(`Entry ${i}: ${int16View[i]}`);
}
Hier erstellen wir eine 16-Bit-Ganzzahl-View, die denselben Buffer wie die vorhandene 32-Bit-View teilt, und wir geben alle Werte im Buffer als 16-Bit-Ganzzahlen aus. Jetzt erhalten wir die Ausgabe 0
, 0
, 2
, 0
, 4
, 0
, 6
, 0
(unter der Annahme von Little-Endian-Codierung):
Int16Array | 0 | 0 | 2 | 0 | 4 | 0 | 6 | 0 | Int32Array | 0 | 2 | 4 | 6 | ArrayBuffer | 00 00 00 00 | 02 00 00 00 | 04 00 00 00 | 06 00 00 00 |
Man kann jedoch noch einen Schritt weiter gehen. Betrachten Sie dies:
int16View[0] = 32;
console.log(`Entry 0 in the 32-bit array is now ${int32View[0]}`);
Die Ausgabe hiervon ist "Entry 0 in the 32-bit array is now 32"
.
Mit anderen Worten, die beiden Arrays werden tatsächlich auf denselben Datenpuffer betrachtet und als unterschiedliche Formate behandelt.
Int16Array | 32 | 0 | 2 | 0 | 4 | 0 | 6 | 0 | Int32Array | 32 | 2 | 4 | 6 | ArrayBuffer | 20 00 00 00 | 02 00 00 00 | 04 00 00 00 | 06 00 00 00 |
Sie können dies mit jedem View-Typ tun, obwohl Sie vermutlich ein seltsames Ergebnis erhalten, wenn Sie eine Ganzzahl setzen und dann als eine Fließkommazahl lesen, da die Bits unterschiedlich interpretiert werden.
const float32View = new Float32Array(buffer);
console.log(float32View[0]); // 4.484155085839415e-44
Lesen von Text aus einem Buffer
Buffer repräsentieren nicht immer Zahlen. Zum Beispiel kann das Lesen einer Datei Ihnen einen Textdatenpuffer geben. Sie können diese Daten mit einem Typed Array aus dem Puffer lesen.
Das folgende Beispiel liest UTF-8-Text mithilfe der TextDecoder
Web-API:
const buffer = new ArrayBuffer(8);
const uint8 = new Uint8Array(buffer);
// Data manually written here, but pretend it was already in the buffer
uint8.set([228, 189, 160, 229, 165, 189]);
const text = new TextDecoder().decode(uint8);
console.log(text); // "你好"
Das folgende Beispiel liest UTF-16-Text mit der String.fromCharCode()
Methode:
const buffer = new ArrayBuffer(8);
const uint16 = new Uint16Array(buffer);
// Data manually written here, but pretend it was already in the buffer
uint16.set([0x4f60, 0x597d]);
const text = String.fromCharCode(...uint16);
console.log(text); // "你好"
Arbeiten mit komplexen Datenstrukturen
Durch die Kombination eines einzigen Puffers mit mehreren Views unterschiedlicher Typen, die an verschiedenen Offsets in den Puffer starten, können Sie mit Datenobjekten interagieren, die mehrere Datentypen enthalten. Dies ermöglicht es Ihnen beispielsweise, mit komplexen Datenstrukturen von WebGL oder Datendateien zu interagieren.
Betrachten Sie diese C-Struktur:
struct someStruct {
unsigned long id;
char username[16];
float amountDue;
};
Sie können auf einen Puffer zugreifen, der Daten in diesem Format enthält, wie folgt:
const buffer = new ArrayBuffer(24);
// ... read the data into the buffer ...
const idView = new Uint32Array(buffer, 0, 1);
const usernameView = new Uint8Array(buffer, 4, 16);
const amountDueView = new Float32Array(buffer, 20, 1);
Dann können Sie beispielsweise auf den zu zahlenden Betrag mit amountDueView[0]
zugreifen.
Hinweis: Die Datenstruktur-Ausrichtung in einer C-Struktur ist plattformabhängig. Nehmen Sie Vorsichtsmaßnahmen und Überlegungen zu diesen Padding-Unterschieden in Betracht.
Umwandlung in normale Arrays
Nach der Verarbeitung eines Typed Arrays kann es manchmal nützlich sein, es in ein normales Array zurück zu konvertieren, um von dem Array
Prototyp zu profitieren. Dies kann mit Array.from()
durchgeführt werden:
const typedArray = new Uint8Array([1, 2, 3, 4]);
const normalArray = Array.from(typedArray);
sowie mit der Spread-Syntax:
const typedArray = new Uint8Array([1, 2, 3, 4]);
const normalArray = [...typedArray];
Siehe auch
- Faster Canvas Pixel Manipulation with Typed Arrays auf hacks.mozilla.org (2011)
- Typed arrays - Binary data in the browser auf web.dev (2012)
- Endianness
ArrayBuffer
DataView
TypedArray
SharedArrayBuffer