23 octobre 2022

ArrayBuffer, tableaux binaires

Dans le développement web, nous rencontrons des données binaires principalement lorsque l’on travaille avec des fichiers (création, envoi, téléchargement). Un autre cas d’utilisation est le traitement d’image.

Tout ceci est possible en JavaScript, et les opérations binaires sont très performantes.

Cependant, il y a de la confusion, car il y a beaucoup de classes disponibles. Pour en nommer quelques unes:

  • ArrayBuffer, Uint8Array, DataView, Blob, File, etc.

En javascript, les données binaires sont implémentées de façon non standard, comparé à d’autres langages. Mais quand nous mettons de l’ordre dans tout ça, tout devient beaucoup plus simple.

L’objet binaire de base est un ArrayBuffer – une référence à une zone contigüe de taille fixe de la mémoire.

Nous le créons comme ceci:

let buffer = new ArrayBuffer(16); // crée un Buffer de taille 16
alert(buffer.byteLength); // 16

Cela alloue une zone contigue de 16 octets dans la mémoire et la pré-remplie avec des zéros.

L’ArrayBuffer n’est pas un tableau de ‘quelque chose’.

Commençons par éliminer une possible source de confusion. ArrayBuffer n’a rien en commun avec Array:

  • Il possède une taille fixe, nous ne pouvons ni l’aggrandir, ni le réduire.
  • Il prend une taille spécifique en mémoire.
  • Pour accéder à des octets individuels, un autre objet de “vue” est nécessaire, on n’utilise pas buffer[index].

ArrayBuffer est une zone de la mémoire. Qui y’a t’il à l’intérieur ? Juste une séquence d’octets.

Pour manipuler un ArrayBuffer, nous avons besoin d’utiliser un objet de “vue”.

Un objet de “vue” ne stocke rien tout seul. Ce sont les lunettes qui donnent une interprétation des octets stockés dans l’ArrayBuffer.

Par exemple:

  • Uint8Array – Traite chaque octet dans l’ArrayBuffer comme un nombre unique, avec des valeurs possibles entre 0 jusqu’à 255 (Un octet est sur 8 bits, donc ça ne peut contenir que ça). On appelle ces valeurs des “entiers non signés sur 8 bits”.
  • Uint16Array – Traite par paquet de 2 octets en tant qu’entier, avec des valeurs possibles entre 0 jusqu’à 65535. On appelle ces valeurs des “entiers non signés sur 16 bits”.
  • Uint32Array – Traite par paquet de 4 octets en tant qu’entier, avec des valeurs possibles entre 0 jusqu’à 4294967295. On appelle ces valeurs des “entiers non signés sur 32bits”.
  • Float64Array – Traite par paquet de 8 octets en tant que nombre flottant avec des valeurs possibles entre 5.0x10-324 et 1.8x10308.

Donc, les données binaires dans un ArrayBuffer de 16 octets peuvent être interprétées comme 16 “petits nombres” , ou 8 grands nombres (2 octets chacun), ou 4 encore plus grands (4 octets chacun), ou 2 valeurs flottantes avec une haute précision (8 octets chacun).

ArrayBuffer est l’objet central, le centre de tout, les données binaires brutes.

Mais si nous voulons écrire à l’intérieur, ou itérer dessus, pour n’importe quelle opération – nous devons utiliser une “vue”, e.g:

let buffer = new ArrayBuffer(16); // crée un buffer de taille 16

let view = new Uint32Array(buffer); // Traite le buffer en une séquence d'entiers de 32 bits.

alert(Uint32Array.BYTES_PER_ELEMENT); // 4 octets par entier.

alert(view.length); // 4, il stocke cette quantité d'entiers.
alert(view.byteLength); // 16, la taille en octets.

// Ecrivons une valeur
view[0] = 123456;

// Itérons sur les valeurs
for(let num of view) {
  alert(num); // 123456, puis 0, 0, 0 (4 valeurs au total)
}

TypedArray – tableau typé

Le terme commun pour toutes ces vues (Uint8Array, Uint32Array, etc) est TypedArray. Elles partagent le même ensemble de méthodes et de propriétés.

Il faut noter qu’il n’y a pas de construteur appelé TypedArray, Il s’agit d’un terme pour représenter une des vues par dessus un ArrayBuffer: Int8Array, Uint8Array etc. La liste entière va bientôt suivre.

Lorsque vous voyez quelque chose comme new TypedArray, Il s’agit de n’importe quoi parmi new Int8Array, new Uint8Array, etc.

Les tableaux typés ressemblent à des tableaux classiques : ils ont des indexs et sont itérables.

Un constructeur TypedArray (soit Int8Array ou Float64Array, peut importe) se comporte différement en fonction du type des arguments.

Il y a 5 variantes d’arguments:

new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
  1. Si un ArrayBuffer est fourni, la vue est créée dessus. Nous avons déjà utilisé cette syntaxe.

    Nous pouvons éventuellement fournir un décalage (byteOffset) pour commencer à partir de là (0 par défaut) et la longueur (length) (jusqu’à la fin du buffer par défaut), alors la vue ne va couvrir qu’une partie du buffer.