`DataView` en TypeScript
Cet article explique DataView en TypeScript.
Nous allons expliquer DataView en TypeScript, des bases à l’utilisation pratique, étape par étape.
YouTube Video
DataView en TypeScript
En utilisant DataView en TypeScript, vous pouvez effectuer des opérations de lecture et d’écriture précises au niveau des octets sur un ArrayBuffer. DataView est extrêmement utile pour le traitement binaire de bas niveau, comme la mise en œuvre de protocoles, l’analyse de fichiers binaires, et l’envoi/la réception de données binaires via WebSocket.
Concepts de base : différences entre ArrayBuffer, TypedArray et DataView
ArrayBuffer est une structure de données fondamentale utilisée pour stocker une séquence d’octets de longueur fixe. Les TypedArrays comme Uint8Array sont des vues qui interprètent le buffer comme un tableau d’un type spécifique, dont chaque élément possède ce type fixe.
D’autre part, DataView est une vue flexible qui permet de lire et d’écrire des valeurs de tout type de données à n’importe quelle position de décalage. Comme il ne suppose pas de types fixes et permet un contrôle précis au niveau de l’octet, il est adapté à l’analyse de protocoles binaires et au traitement de bas niveau.
Voici un exemple de création d’un ArrayBuffer, puis de création d’un Uint8Array et d’un DataView à partir de celui-ci.
1// Create an ArrayBuffer (8 bytes)
2const buffer = new ArrayBuffer(8);
3
4// Create a Uint8Array view (easy to manipulate individual bytes)
5const u8 = new Uint8Array(buffer);
6u8[0] = 0x12;
7u8[1] = 0x34;
8
9// Create a DataView (allows reading/writing any type at any offset)
10const dv = new DataView(buffer);
11
12// Read a 16-bit unsigned integer (big-endian assumed if second argument is omitted)
13console.log(dv.getUint16(0)); // 0x1234
- Dans ce code, deux types de vues différents—
Uint8ArrayetDataView—sont utilisés simultanément sur un même buffer. AvecDataView, vous pouvez lire et écrire des valeurs de manière flexible en spécifiant des décalages et des bouts (endianness) arbitraires.
Principales méthodes de DataView
DataView est une interface pour manipuler un ArrayBuffer au niveau des octets et fournit des méthodes pour lire et écrire divers types tels que les entiers et les nombres à virgule flottante.
Les principales méthodes sont les suivantes :.
- Lecture :
getUint8,getInt8,getUint16,getInt16,getUint32,getInt32,getFloat32,getFloat64 - Écriture :
setUint8,setInt8,setUint16,setInt16,setUint32,setInt32,setFloat32,setFloat64
Toutes ces méthodes prennent un « décalage d’octet » en premier argument : get lit la valeur à cette position, et set écrit la valeur à cette position. Vous pouvez également spécifier l’endianness avec le deuxième argument lors de la manipulation de données sur 16, 32 ou 64 bits. En pratique, il est préférable de toujours spécifier l’endianness conformément à la spécification des données.
L’exemple suivant montre comment écrire un entier 32 bits et un nombre à virgule flottante 32 bits en format little endian dans un buffer, puis les relire dans le même format.
1const buf = new ArrayBuffer(12); // Enough space for integer + float
2const view = new DataView(buf);
3
4// Write a 32-bit unsigned integer at offset 0 (little-endian)
5view.setUint32(0, 305419896, true); // 305419896 = 0x12345678
6
7// Read the same 32-bit unsigned integer back from offset 0 (little-endian)
8const intVal = view.getUint32(0, true);
9console.log(intVal); // 305419896
10
11// Write a 32-bit floating number at offset 4 (little-endian)
12view.setFloat32(4, 3.1415926, true);
13
14// Read the 32-bit floating number back from offset 4 (little-endian)
15const floatVal = view.getFloat32(4, true);
16console.log(floatVal); // 3.1415926 (approx)
- En spécifiant explicitement l’endianness, vous pouvez garantir la compatibilité avec différentes plateformes et spécifications binaires.
À propos de l’endianness (ordre des octets)
Certains protocoles, comme ceux utilisés en réseau, emploient le big-endian tandis que de nombreux processeurs et formats de fichier utilisent principalement le little-endian. Dans DataView, si le second argument est défini à true, les données sont interprétées en little endian ; s’il est défini à false ou omis, elles sont interprétées en big endian.
Dans l’exemple suivant, vous pouvez voir comment le tableau d’octets en mémoire change lorsque vous écrivez le même nombre en utilisant à la fois le format big-endian et little-endian.
1const b = new ArrayBuffer(4);
2const a = new Uint8Array(b);
3const dv = new DataView(b);
4
5// Write in big-endian order
6dv.setUint32(0, 0x0A0B0C0D, false); // big-endian
7console.log([...a]); // [10, 11, 12, 13]
8
9// Write the same value in little-endian order
10dv.setUint32(0, 0x0A0B0C0D, true); // little-endian
11console.log([...a]); // [13, 12, 11, 10]
- Comprendre comment l’ordre des octets change entre big endian et little endian facilitera grandement l’analyse des données de communication ou des formats binaires.
Entrée/Sortie de chaînes (utilisation conjointe de TextEncoder et Decoder)
DataView est excellent pour lire et écrire des nombres, mais il ne gère pas directement les chaînes de caractères. Il est courant d’encoder les chaînes avec TextEncoder ou TextDecoder (par exemple en UTF-8), puis de les copier dans le buffer via Uint8Array. Voici un exemple d’écriture d’une chaîne UTF-8 dans un buffer, puis de sa lecture.
1const encoder = new TextEncoder();
2const decoder = new TextDecoder();
3
4const str = "\u3053\u3093\u306b\u3061\u306f";
5const encoded = encoder.encode(str); // Uint8Array (UTF-8 encoded bytes)
6
7// Create a buffer and write the encoded string bytes into it
8const buf2 = new ArrayBuffer(encoded.length);
9const u8v = new Uint8Array(buf2);
10u8v.set(encoded);
11
12// Read the bytes and decode them back into a string
13const decodedStr = decoder.decode(new Uint8Array(buf2));
14console.log(decodedStr);- En convertissant les chaînes en binaire et en préfixant éventuellement leur longueur, vous pouvez stocker des chaînes de longueur variable.
Exemple pratique : encodage/décodage d’un format binaire personnalisé
Ci-dessous, nous définissons un format de message simple contenant un numéro de version, un identifiant et un nom. Nous implémentons un processus d’encodage pour convertir les messages en données binaires et un processus de décodage pour restaurer l’objet original à partir du binaire. Dans ce format de message, le premier octet stocke la version, les 4 octets suivants stockent l’identifiant au format little-endian, l’octet suivant contient la longueur du nom et enfin, le nom encodé en UTF-8 est stocké.
1// Encode an object into a binary message
2function encodeMessage(msg: { version: number; id: number; name: string }): ArrayBuffer {
3 const encoder = new TextEncoder();
4 const nameBytes = encoder.encode(msg.name);
5 const total = 1 + 4 + 1 + nameBytes.length;
6 const buf = new ArrayBuffer(total);
7 const dv = new DataView(buf);
8 let offset = 0;
9
10 dv.setUint8(offset, msg.version); offset += 1; // write version
11 dv.setUint32(offset, msg.id, true); offset += 4; // write ID (little-endian)
12 dv.setUint8(offset, nameBytes.length); offset += 1; // write name length
13
14 // write name bytes
15 new Uint8Array(buf, offset).set(nameBytes);
16 return buf;
17}
18
19// Decode a binary message back into an object
20function decodeMessage(buf: ArrayBuffer) {
21 const dv = new DataView(buf);
22 let offset = 0;
23
24 const version = dv.getUint8(offset); offset += 1; // read version
25 const id = dv.getUint32(offset, true); offset += 4; // read ID (little-endian)
26 const nameLen = dv.getUint8(offset); offset += 1; // read name length
27
28 // extract name bytes
29 const nameBytes = new Uint8Array(buf, offset, nameLen);
30 const decoder = new TextDecoder();
31 // decode UTF-8 string
32 const name = decoder.decode(nameBytes);
33
34 return { version, id, name };
35}
36
37// Example usage
38const packed = encodeMessage({ version: 1, id: 42, name: "Alice" });
39const unpacked = decodeMessage(packed);
40console.log(unpacked); // { version: 1, id: 42, name: "Alice" }
- C’est une implémentation typique pour traiter un format de message de base contenant une chaîne à longueur variable, et elle peut être appliquée dans de nombreuses situations réelles, comme la communication réseau ou la conception de fichiers binaires personnalisés.
Précautions et bonnes pratiques
Lorsque vous travaillez avec des données binaires à l’aide de DataView, il y a plusieurs points pratiques à prendre en compte, au-delà de la simple lecture/écriture des valeurs : sécurité, cohérence de l’endianness et gestion correcte des types notamment. En particulier, lors de la manipulation de données binaires provenant de sources externes ou de grands entiers, il est important de concevoir votre code pour éviter les lectures incorrectes et les dépassements de tampon. Voici quelques points utiles à retenir pour l’utilisation pratique.
-
Vérification des limites
DataViewlance une exception si un décalage ou une taille dépasse les limites du buffer. Vérifiez toujours les longueurs lors de la manipulation de données binaires non fiables. -
Toujours spécifier l’endianness Spécifiez toujours clairement et systématiquement little endian ou big endian dans votre code.
-
Cohérence des types Les nombres JavaScript sont des valeurs à virgule flottante IEEE-754 sur 64 bits. Les méthodes comme
getUint32sont prises en charge, mais il n’existe pas degetUint64, un traitement spécial est donc nécessaire pour les entiers sur 64 bits. -
Gestion des entiers 64 bits Pour traiter des entiers sur 64 bits, vous devez utiliser
BigIntou diviser manuellement la valeur en parties hautes et basses de 32 bits. Voici un exemple simple de lecture d’un entier non signé sur 64 bits.
1function getUint64(dv: DataView, offset: number, littleEndian = true): bigint {
2 const low = BigInt(dv.getUint32(offset, littleEndian));
3 const high = BigInt(dv.getUint32(offset + 4, littleEndian));
4 return littleEndian ? (high << 32n) | low : (low << 32n) | high;
5}- En utilisant
BigInt, vous pouvez également gérer en toute sécurité des entiers supérieurs à 64 bits.
Utilisation dans Node.js (interopérabilité avec Buffer)
Bien que Buffer soit couramment utilisé dans Node.js, il est également facile de convertir entre ArrayBuffer et DataView. Utilisez la propriété buffer des objets Buffer ou le constructeur Uint8Array pour la conversion.
1// Node.js: Buffer -> ArrayBuffer
2const nodeBuf = Buffer.from([1,2,3,4]);
3const arrBuf = nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.byteLength);
4const dvNode = new DataView(arrBuf);
5console.log(dvNode.getUint16(0));- Ceci peut servir d’outil pour échanger des données binaires entre Node.js et les navigateurs.
Résumé
DataView est un mécanisme puissant pour lire et écrire librement des données binaires—particulièrement utile dans les situations nécessitant un contrôle flexible, comme la spécification de l’endianness ou l’accès à des positions arbitraires. En combinant ArrayBuffer, TypedArray et DataView, vous pouvez gérer des données binaires de manière flexible et précise avec TypeScript, ce qui permet une large gamme d’utilisations, de la mise en œuvre de protocoles à l’analyse de fichiers. En intégrant également l’encodage de chaînes et la gestion des entiers 64 bits selon les besoins, vous pouvez réaliser des opérations binaires encore plus pratiques.
Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.