Utilisation des tampons en Node.js

L’auteur a sélectionné le Fonds de secours COVID-19 pour recevoir une donation dans le cadre du programme Write for DOnations.

Introduction

A buffer is a space in memory (typically RAM) that stores binary data. In Node.js, we can access these spaces of memory with the built-in Buffer class. Buffers store a sequence of integers, similar to an array in JavaScript. Unlike arrays, you cannot change the size of a buffer once it is created.

Vous avez peut-être déjà utilisé des tampons implicitement si vous avez déjà écrit du code Node.js. Par exemple, lorsque vous lisez à partir d’un fichier avec fs.readFile(), les données renvoyées au retour d’appel ou à la promesse sont un objet tampon. De plus, lorsque des requêtes HTTP sont effectuées en Node.js, elles renvoient des flux de données qui sont temporairement stockés dans un tampon interne lorsque le client ne peut pas traiter le flux en une seule fois.

Les tampons sont utiles lorsque vous interagissez avec des données binaires, généralement aux niveaux inférieurs du réseau. Ils vous permettent également d’effectuer une manipulation de données fine dans Node.js.

Dans ce tutoriel, vous utiliserez le Node.js REPL pour parcourir divers exemples de tampons, tels que la création de tampons, la lecture à partir de tampons, l’écriture dans des tampons, la copie à partir de tampons et l’utilisation de tampons pour convertir entre des données binaires et encodées. À la fin du tutoriel, vous aurez appris à utiliser la classe Buffer pour travailler avec des données binaires.

Prérequis

Étape 1 — Création d’un tampon (buffer)

Cette première étape vous montrera les deux principales façons de créer un objet tampon (buffer) dans Node.js.

Pour décider quelle méthode utiliser, vous devez répondre à cette question : souhaitez-vous créer un nouveau tampon ou extraire un tampon à partir de données existantes ? Si vous allez stocker des données en mémoire que vous n’avez pas encore reçues, vous voudrez créer un nouveau tampon. Dans Node.js, nous utilisons la fonction alloc() de la Buffer classe pour ce faire.

Ouvrons le REPL Node.js pour voir par nous-mêmes. Dans votre terminal, saisissez la commande node :

  1. node

Vous verrez l’invite commencer par >.

La fonction alloc() prend la taille du tampon comme premier et seul argument requis. La taille est un entier représentant combien d’octets de mémoire l’objet tampon utilisera. Par exemple, si nous voulions créer un tampon de 1 Ko (kilooctet), équivalent à 1024 octets, nous entrerions ceci dans la console :

  1. const firstBuf = Buffer.alloc(1024);

Pour créer un nouveau tampon, nous avons utilisé la classe Buffer disponible globalement, qui possède la méthode alloc(). En fournissant 1024 comme argument pour alloc(), nous avons créé un tampon de 1 Ko.

Par défaut, lorsque vous initialisez un tampon avec alloc(), le tampon est rempli de zéros binaires comme espace réservé pour les données ultérieures. Cependant, nous pouvons changer la valeur par défaut si nous le souhaitons. Si nous voulions créer un nouveau tampon avec des 1 au lieu des 0, nous définirions le second paramètre de la fonction alloc()fill.

Dans votre terminal, créez un nouveau tampon à l’invite REPL qui est rempli de 1s :

  1. const filledBuf = Buffer.alloc(1024, 1);

Nous venons de créer un nouvel objet tampon qui référence un espace en mémoire contenant 1 Ko de 1s. Bien que nous ayons saisi un entier, toutes les données stockées dans un tampon sont des données binaires.

Les données binaires peuvent avoir de nombreux formats différents. Par exemple, considérons une séquence binaire représentant un octet de données : 01110110. Si cette séquence binaire représentait une chaîne en anglais en utilisant la norme de codage ASCII, ce serait la lettre v. Cependant, si notre ordinateur traitait une image, cette séquence binaire pourrait contenir des informations sur la couleur d’un pixel.

L’ordinateur sait les traiter différemment parce que les octets sont encodés différemment. L’encodage des octets est le format de l’octet. Un tampon en Node.js utilise le schéma d’encodage UTF-8 par défaut s’il est initialisé avec des données de chaîne de caractères. Un octet en UTF-8 représente un nombre, une lettre (en anglais et dans d’autres langues) ou un symbole. UTF-8 est un sur-ensemble de ASCII, le Code américain normalisé pour l’échange d’informations. ASCII peut encoder des octets avec des lettres anglaises majuscules et minuscules, les chiffres 0 à 9, et quelques autres symboles comme le point d’exclamation (!) ou le signe et commercial (&).

Si nous écrivions un programme qui ne pouvait fonctionner qu’avec des caractères ASCII, nous pourrions changer l’encodage utilisé par notre tampon avec le troisième argument de la fonction alloc()encodage.

Créons un nouveau tampon qui mesure cinq octets de long et stocke uniquement des caractères ASCII:

  1. const asciiBuf = Buffer.alloc(5, 'a', 'ascii');

Le tampon est initialisé avec cinq octets du caractère a, en utilisant la représentation ASCII.

Remarque: Par défaut, Node.js prend en charge les encodages de caractères suivants:

  • ASCII, représenté par ascii
  • UTF-8, représenté par utf-8 ou utf8
  • UTF-16, représenté par utf-16le ou utf16le
  • UCS-2, représenté comme ucs-2 ou ucs2
  • Base64, représenté comme base64
  • Hexadécimal, représenté comme hex
  • ISO/IEC 8859-1, représenté comme latin1 ou binary

Toutes ces valeurs peuvent être utilisées dans les fonctions de la classe Buffer qui acceptent un paramètre encoding. Par conséquent, ces valeurs sont toutes valides pour la méthode alloc().

Jusqu’à présent, nous avons créé de nouveaux tampons avec la fonction alloc(). Mais parfois, nous pouvons vouloir créer un tampon à partir de données qui existent déjà, comme une chaîne ou un tableau.

Pour créer un tampon à partir de données préexistantes, nous utilisons la méthode from(). Nous pouvons utiliser cette fonction pour créer des tampons à partir de :

  • Un tableau d’entiers : Les valeurs entières peuvent être comprises entre 0 et 255.
  • Un ArrayBuffer : Il s’agit d’un objet JavaScript qui stocke une longueur fixe d’octets.
  • A string.
  • Un autre tampon.
  • D’autres objets JavaScript qui ont une propriété Symbol.toPrimitive. Cette propriété indique à JavaScript comment convertir l’objet en un type de données primitif : boolean, null, undefined, number, string ou symbol. Vous pouvez en savoir plus sur les symboles dans la documentation JavaScript de Mozilla.

Voyons maintenant comment nous pouvons créer un tampon à partir d’une chaîne de caractères. Dans l’invite Node.js, saisissez ceci :

  1. const stringBuf = Buffer.from('My name is Paul');

Nous avons maintenant un objet tampon créé à partir de la chaîne My name is Paul. Créons maintenant un nouveau tampon à partir d’un autre tampon que nous avons créé précédemment :

  1. const asciiCopy = Buffer.from(asciiBuf);

Nous avons maintenant créé un nouveau tampon asciiCopy qui contient les mêmes données que asciiBuf.

Maintenant que nous avons créé des tampons, nous pouvons plonger dans des exemples de lecture de leurs données.

Étape 2 — Lecture d’un tampon

Il existe de nombreuses façons d’accéder aux données dans un tampon. Nous pouvons accéder à un octet individuel dans un tampon ou nous pouvons extraire l’ensemble du contenu.

Pour accéder à un octet d’un tampon, nous passons l’index ou l’emplacement de l’octet que nous voulons. Les tampons stockent les données de manière séquentielle comme les tableaux. Ils indexent également leurs données comme les tableaux, en commençant à 0. Nous pouvons utiliser la notation de tableau sur l’objet tampon pour obtenir un octet individuel.

Voyons ce que cela donne en créant un tampon à partir d’une chaîne dans le REPL :

  1. const hiBuf = Buffer.from('Hi!');

Maintenant, lisons le premier octet du tampon :

  1. hiBuf[0];

En appuyant sur ENTRÉE, le REPL affichera :

Output
72

L’entier 72 correspond à la représentation UTF-8 de la lettre H.

Remarque : Les valeurs des octets peuvent être des nombres entre 0 et 255. Un octet est une séquence de 8 bits. Un bit est binaire, et donc ne peut avoir qu’une de deux valeurs : 0 ou 1. Si nous avons une séquence de 8 bits et deux valeurs possibles par bit, alors nous avons un maximum de 2⁸ valeurs possibles pour un octet. Cela donne un maximum de 256 valeurs. Puisque nous commençons à compter à partir de zéro, cela signifie que notre nombre le plus élevé est 255.

Faisons de même pour le deuxième octet. Entrez ce qui suit dans le REPL :

  1. hiBuf[1];

Le REPL renvoie 105, ce qui représente la minuscule i.

Enfin, obtenons le troisième caractère :

  1. hiBuf[2];

Vous verrez 33 affiché dans le REPL, ce qui correspond à !.

Essayons de récupérer un octet à partir d’un index invalide :

  1. hiBuf[3];

Le REPL retournera :

Output
undefined

C’est comme si nous essayions d’accéder à un élément dans un tableau avec un index incorrect.

Maintenant que nous avons vu comment lire les octets individuels d’un tampon, voyons nos options pour récupérer toutes les données stockées dans un tampon en une seule fois. L’objet tampon est livré avec les méthodes toString() et toJSON(), qui renvoient l’ensemble du contenu d’un tampon dans deux formats différents.

Comme son nom l’indique, la méthode toString() convertit les octets du tampon en une chaîne de caractères et la renvoie à l’utilisateur. Si nous utilisons cette méthode sur hiBuf, nous obtiendrons la chaîne Hi!. Essayons !

Dans l’invite, saisissez :

  1. hiBuf.toString();

Le REPL renverra :

Output
'Hi!'

Ce tampon a été créé à partir d’une chaîne de caractères. Voyons ce qui se passe si nous utilisons la méthode toString() sur un tampon qui n’a pas été créé à partir de données de type chaîne de caractères.

Créons maintenant un nouveau tampon vide de taille 10 octets :

  1. const tenZeroes = Buffer.alloc(10);

Maintenant, utilisons la méthode toString() :

  1. tenZeroes.toString();

Nous verrons le résultat suivant :

'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'

La chaîne \u0000 correspond au caractère Unicode pour NULL. Il correspond au nombre 0. Lorsque les données du tampon ne sont pas encodées sous forme de chaîne de caractères, la méthode toString() renvoie l’encodage UTF-8 des octets.

La méthode toString() possède un paramètre optionnel, encoding. Nous pouvons utiliser ce paramètre pour changer l’encodage des données du tampon qui est retourné.

Par exemple, si vous vouliez obtenir l’encodage hexadécimal pour hiBuf, vous entreriez ce qui suit dans l’invite :

  1. hiBuf.toString('hex');

Cette instruction évaluera à :

Output
'486921'

486921 est la représentation hexadécimale des octets qui représentent la chaîne Hi!. En Node.js, lorsque les utilisateurs veulent convertir l’encodage des données d’une forme à une autre, ils mettent généralement la chaîne dans un tampon et appellent toString() avec l’encodage désiré.

La méthode toJSON() se comporte différemment. Peu importe si le tampon a été créé à partir d’une chaîne de caractères ou non, elle renvoie toujours les données sous forme de représentation entière de l’octet.

Réutilisons les tampons hiBuf et tenZeroes pour pratiquer l’utilisation de toJSON(). À l’invite, saisissez :

  1. hiBuf.toJSON();

Le REPL renverra :

Output
{ type: 'Buffer', data: [ 72, 105, 33 ] }

L’objet JSON a une propriété type qui sera toujours Buffer. Cela permet aux programmes de distinguer ces objets JSON des autres objets JSON.

La propriété data contient un tableau de la représentation entière des octets. Vous avez peut-être remarqué que 72, 105 et 33 correspondent aux valeurs que nous avons obtenues lorsque nous avons extrait individuellement les octets.

Essayons maintenant la méthode toJSON() avec tenZeroes :

  1. tenZeroes.toJSON();

Dans le REPL, vous verrez ce qui suit :

Output
{ type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }

Le type est le même que celui noté précédemment. Cependant, les données sont maintenant un tableau avec dix zéros.

Maintenant que nous avons couvert les principales façons de lire à partir d’un tampon, regardons comment nous modifions le contenu d’un tampon.

Étape 3 — Modification d’un tampon

Il existe de nombreuses façons de modifier un objet tampon existant. Tout comme pour la lecture, nous pouvons modifier les octets du tampon individuellement en utilisant la syntaxe des tableaux. Nous pouvons également écrire de nouveaux contenus dans un tampon, remplaçant les données existantes.

Commençons par voir comment nous pouvons modifier des octets individuels d’un tampon. Rappelez-vous notre variable de tampon hiBuf, qui contient la chaîne Salut !. Changeons chaque octet pour qu’il contienne plutôt Hey.

Dans le REPL, essayons d’abord de définir le deuxième élément de hiBuf sur e:

  1. hiBuf[1] = 'e';

Maintenant, voyons ce tampon sous forme de chaîne pour confirmer qu’il stocke les bonnes données. Ensuite, appelons la méthode toString():

  1. hiBuf.toString();

Cela sera évalué comme suit:

Output
'H\u0000!'

Nous avons reçu cette sortie étrange car le tampon ne peut accepter qu’une valeur entière. Nous ne pouvons pas lui attribuer la lettre e; plutôt, nous devons lui attribuer le nombre dont l’équivalent binaire représente e:

  1. hiBuf[1] = 101;

Maintenant, lorsque nous appelons la méthode toString():

  1. hiBuf.toString();

Nous obtenons cette sortie dans le REPL:

Output
'He!'

Pour changer le dernier caractère dans le tampon, nous devons définir le troisième élément sur l’entier qui correspond à l’octet pour y:

  1. hiBuf[2] = 121;

Confirmons en utilisant à nouveau la méthode toString():

  1. hiBuf.toString();

Votre REPL affichera:

Output
'Hey'

Si nous essayons d’écrire un octet qui se trouve en dehors de la plage du tampon, il sera ignoré et le contenu du tampon ne changera pas. Par exemple, essayons de définir le quatrième élément inexistant du tampon sur o:

  1. hiBuf[3] = 111;

Nous pouvons confirmer que le tampon est inchangé avec la méthode toString():

  1. hiBuf.toString();

La sortie est toujours:

Output
'Hey'

Si nous voulions changer le contenu de l’intégralité du tampon, nous pouvons utiliser la méthode write(). La méthode write() accepte une chaîne de caractères qui remplacera le contenu du tampon.

Utilisons la méthode write() pour changer le contenu de hiBuf en Hi!. Dans votre shell Node.js, tapez la commande suivante à l’invite :

  1. hiBuf.write('Hi!');

La méthode write() a retourné 3 dans le REPL. Cela est dû au fait qu’elle a écrit trois octets de données. Chaque lettre a une taille d’un octet, puisque ce tampon utilise un codage UTF-8, qui utilise un octet pour chaque caractère. Si le tampon utilisait un codage UTF-16, qui a un minimum de deux octets par caractère, alors la fonction write() aurait retourné 6.

Maintenant, vérifiez le contenu du tampon en utilisant toString() :

  1. hiBuf.toString();

Le REPL produira :

Output
'Hi!'

C’est plus rapide que de devoir changer chaque élément octet par octet.

Si vous essayez d’écrire plus d’octets que la taille d’un tampon, l’objet tampon n’acceptera que ce qui rentre. Pour illustrer, créons un tampon qui stocke trois octets :

  1. const petBuf = Buffer.alloc(3);

Maintenant, tentons d’écrire Cats dessus :

  1. petBuf.write('Cats');

Lorsque l’appel à write() est évalué, le REPL retourne 3, ce qui indique que seuls trois octets ont été écrits dans le tampon. Confirmons maintenant que le tampon contient les trois premiers octets :

  1. petBuf.toString();

Le REPL retourne :

Output
'Cat'

La fonction write() ajoute les octets dans l’ordre séquentiel, donc seuls les trois premiers octets ont été placés dans le tampon.

En revanche, créons un Buffer qui stocke quatre octets:

  1. const petBuf2 = Buffer.alloc(4);

Écrivez les mêmes contenus dessus:

  1. petBuf2.write('Cats');

Ensuite, ajoutez du nouveau contenu qui occupe moins d’espace que le contenu original:

  1. petBuf2.write('Hi');

Comme les tampons écrivent séquentiellement, en commençant par 0, si nous imprimons le contenu du tampon:

  1. petBuf2.toString();

Nous serions accueillis avec:

Output
'Hits'

Les deux premiers caractères sont écrasés, mais le reste du tampon est intact.

Parfois, les données que nous voulons dans notre tampon préexistant ne sont pas dans une chaîne mais résident dans un autre objet tampon. Dans ces cas, nous pouvons utiliser la fonction copy() pour modifier ce que notre tampon stocke.

Créons donc deux nouveaux tampons:

  1. const wordsBuf = Buffer.from('Banana Nananana');
  2. const catchphraseBuf = Buffer.from('Not sure Turtle!');

Les tampons wordsBuf et catchphraseBuf contiennent tous deux des données de chaîne. Nous voulons modifier catchphraseBuf pour qu’il stocke Nananana Turtle! au lieu de Not sure Turtle!. Nous utiliserons copy() pour obtenir Nananana de wordsBuf à catchphraseBuf.

Pour copier des données d’un tampon à l’autre, nous utiliserons la méthode copy() sur le tampon qui est la source de l’information. Par conséquent, comme wordsBuf contient les données de chaîne que nous voulons copier, nous devons copier comme ceci:

  1. wordsBuf.copy(catchphraseBuf);

Le paramètre target dans ce cas est le tampon catchphraseBuf.

Lorsque nous introduisons cela dans le REPL, il renvoie 15, ce qui indique que 15 octets ont été écrits. La chaîne Nananana utilise seulement 8 octets de données, donc nous savons immédiatement que notre copie n’a pas été effectuée comme prévu. Utilisez la méthode toString() pour voir le contenu de catchphraseBuf:

  1. catchphraseBuf.toString();

Le REPL renvoie:

Output
'Banana Nananana!'

Par défaut, copy() a pris l’intégralité du contenu de wordsBuf et l’a placé dans catchphraseBuf. Nous devons être plus sélectifs pour notre objectif et ne copier que Nananana. Réécrivons le contenu original de catchphraseBuf avant de continuer:

  1. catchphraseBuf.write('Not sure Turtle!');

La fonction copy() a quelques paramètres supplémentaires qui nous permettent de personnaliser quelles données sont copiées vers l’autre tampon. Voici une liste de tous les paramètres de cette fonction:

  • cible – C’est le seul paramètre requis de copy(). Comme nous l’avons vu dans notre utilisation précédente, c’est le tampon que nous voulons copier.
  • targetStart – C’est l’indice des octets dans le tampon cible où nous devrions commencer la copie. Par défaut, c’est 0, ce qui signifie qu’il copie les données à partir du début du tampon.
  • sourceStart – C’est l’indice des octets dans le tampon source à partir duquel nous devrions copier.
  • sourceEnd – C’est l’indice des octets dans le tampon source où nous devrions arrêter la copie. Par défaut, c’est la longueur du tampon.

Donc, pour copier Nananana de wordsBuf dans catchphraseBuf, notre cible devrait être catchphraseBuf comme précédemment. Le débutCible serait 0 car nous voulons que Nananana apparaisse au début de catchphraseBuf. Le débutSource devrait être 7 car c’est l’index où Nananana commence dans wordsBuf. La finSource continuerait d’être la longueur des tampons.

À l’invite REPL, copiez le contenu de wordsBuf comme ceci :

  1. wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length);

La REPL confirme que 8 octets ont été écrits. Remarquez comment wordsBuf.length est utilisé comme valeur pour le paramètre finSource. Comme pour les tableaux, la propriété length nous donne la taille du tampon.

Maintenant, voyons le contenu de catchphraseBuf :

  1. catchphraseBuf.toString();

La REPL retourne :

Output
'Nananana Turtle!'

Succès ! Nous avons pu modifier les données de catchphraseBuf en copiant le contenu de wordsBuf.

Vous pouvez quitter le REPL Node.js si vous le souhaitez. Notez que toutes les variables qui ont été créées ne seront plus disponibles lorsque vous le ferez :

  1. .exit

Conclusion

Dans ce tutoriel, vous avez appris que les tampons sont des allocations de longueur fixe en mémoire qui stockent des données binaires. Vous avez d’abord créé des tampons en définissant leur taille en mémoire et en les initialisant avec des données préexistantes. Ensuite, vous avez lu des données à partir d’un tampon en examinant leurs octets individuels et en utilisant les méthodes toString() et toJSON(). Enfin, vous avez modifié les données stockées par un tampon en changeant ses octets individuels et en utilisant les méthodes write() et copy().

Les tampons vous donnent un excellent aperçu de la manière dont les données binaires sont manipulées par Node.js. Maintenant que vous pouvez interagir avec les tampons, vous pouvez observer les différentes façons dont le codage des caractères affecte la manière dont les données sont stockées. Par exemple, vous pouvez créer des tampons à partir de données de chaîne qui ne sont pas encodées en UTF-8 ou en ASCII et observer leur différence de taille. Vous pouvez également prendre un tampon avec UTF-8 et utiliser toString() pour le convertir en d’autres schémas de codage.

Pour en savoir plus sur les tampons dans Node.js, vous pouvez consulter la documentation de Node.js sur l’objet Buffer. Si vous souhaitez continuer à apprendre Node.js, vous pouvez revenir à la série Comment coder en Node.js, ou parcourir les projets de programmation et les configurations sur notre page thématique Node.

Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js