L'auteur a choisi le COVID-19 Relief Fund pour recevoir un don dans le cadre du programme Write for DOnations.

Introduction

Un buffer est un espace en mémoire (généralement de la RAM) qui stocke des données binaires. Dans Node.js, nous pouvons accéder à ces espaces de mémoire grâce à la classe Buffer intégrée. Les buffers stockent une séquence d'entiers, comme un array en JavaScript. Contrairement aux arrays, vous ne pouvez pas modifier la taille d'un buffer une fois qu'il est créé.

Vous avez peut-être utilisé des buffers sans vous en rendre compte si vous avez déjà écrit du code de Node.js. Par exemple, lorsque vous lisez à partir d'un fichier avec fs.readFile(), les données renvoyées au callback ou à la Promise sont un objet buffer. En outre, lorsque des requêtes HTTP sont effectuées dans Node.js, elles renvoient des flux de données qui sont temporairement stockés dans un buffer interne lorsque le client ne peut pas traiter le flux en une seule fois.

Les buffers sont utiles lorsque vous interagissez avec des données binaires, généralement à des niveaux de réseau inférieurs. Ils vous donnent également la possibilité d'effectuer des manipulations fines de données dans Node.js.

Dans ce tutoriel, vous utiliserez Node.js REPL pour parcourir divers exemples de buffers, comme la création de buffers, la lecture de buffers, l'écriture et la copie de buffers, et l'utilisation de buffers pour convertir des données binaires en données codées. À la fin du tutoriel, vous aurez appris comment utiliser la classe Buffer pour travailler avec des données binaires.

Conditions préalables

Étape 1 — Création d'un buffer

Cette première étape vous présentera les deux façons principales de créer un objet buffer dans Node.js.

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

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

  • node

Vous verrez que l'invite commence par >.

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

  • const firstBuf = Buffer.alloc(1024);

Pour créer un nouveau buffer, nous avons utilisé la classe Buffer globalement disponible, qui contient la méthode alloc(). En fournissant 1024 comme argument pour alloc(), nous avons créé un buffer d'une taille de 1 Ko.

Par défaut, lorsque vous initialisez un buffer avec alloc(), le buffer est rempli de zéros binaires en guise d'emplacement pour les données ultérieures. Cependant, nous pouvons modifier la valeur par défaut si nous le souhaitons. Si nous voulions créer un nouveau buffer avec des 1 au lieu des 0, nous définirions le deuxième paramètre de la fonction alloc()fill.

Dans votre terminal, créez un nouveau buffer à l'invite REPL qui est rempli de 1 :

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

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

Les données binaires peuvent se présenter sous de nombreux formats différents. Considérons par exemple une séquence binaire représentant un octet de données : 01110110. Si cette séquence binaire représentait une chaîne en anglais utilisant la norme de codage ASCII, il s'agirait de 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 codés différemment. L'encodage des octets est le format de l'octet. Un buffer dans Node.js utilise le schéma d'encodage UTF-8 par défaut s'il est initialisé avec des données de chaîne. Un octet en UTF-8 représente un nombre, une lettre (en anglais et dans d'autres langues) ou un symbole. L'UTF-8 est un superset de l’ASCII, le code standard américain pour l'échange d'informations. L'ASCII peut coder 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 esperluette (&).

Si nous écrivions un programme qui ne pourrait fonctionner qu'avec des caractères ASCII, nous pourrions modifier l'encodage utilisé par notre buffer avec le troisième argument de la fonction alloc()encoding.

Créons un nouveau buffer de cinq octets de long qui ne stocke que des caractères ASCII :

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

Le buffer 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 codages de caractères suivants :

  • ASCII, représenté sous le nom ascii
  • UTF-8, représenté sous le nom utf-8 ou utf8
  • UTF-16, représenté sous le nom utf-16le ou utf16le
  • UCS-2, représentée sous le nom ucs-2 ou ucs2
  • Base64, représentée sous le nom base64
  • Hexadecimal, représenté sous le nom hex
  • ISO/IEC 8859-1, représenté sous le nom 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 valables pour la méthode alloc().

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

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

  • Un tableau d'entiers : les valeurs des entiers peuvent être comprises entre 0 et 255.
  • Un ArrayBuffer : c'est un objet JavaScript qui stocke une longueur fixe d'octets.
  • Une chaîne.
  • Un autre buffer.
  • 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 primitives : boolean, null, undefined, number, string, or symbol. Vous pouvez en savoir plus sur les symboles en consultant la documentation JavaScript de Mozilla.

Voyons comment nous pouvons créer un buffer à partir d'une chaîne. Dans l'invite Node.js, saisissez ceci :

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

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

  • const asciiCopy = Buffer.from(asciiBuf);

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

Maintenant que nous avons fait l'expérience de la création de buffers, nous pouvons nous plonger dans des exemples de lecture de leurs données.

Étape 2 — Lecture à partir d'un buffer

Il existe de nombreuses façons d'accéder aux données dans un buffer. Nous pouvons accéder à un octet individuel dans un buffer ou nous pouvons extraire le contenu entier.

Pour accéder à un octet d'un buffer, nous passons l'index ou l'emplacement de l'octet que nous voulons. Les buffers stockent les données de manière séquentielle comme des tableaux. Ils indexent également leurs données comme des tableaux, à partir de 0. Nous pouvons utiliser la notation de tableau sur l'objet buffer pour obtenir un octet individuel.

Voyons à quoi cela ressemble en créant un buffer à partir d'une chaîne dans le REPL :

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

Maintenant, lisons le premier octet du buffer :

  • hiBuf[0];

Lorsque vous appuyez sur ENTER, le REPL affiche :

Output
72

L'entier 72 correspond à la représentation UTF-8 pour la lettre H.

Remarque : les valeurs pour les octets peuvent être des nombres entre 0 et 255. Un octet est une séquence de 8 bits. Un bit est binaire, et ne peut donc avoir qu'une seule des 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 correspond à un maximum de 256 valeurs. Comme 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 :

  • hiBuf[1];

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

Enfin, obtenons le troisième caractère :

  • hiBuf[2];

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

Essayons de récupérer un octet d'un index non valide :

  • hiBuf[3];

Le REPL renverra :

Output
undefined

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

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

Comme son nom l'indique, la méthode toString() convertit les octets du buffer 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 :

  • hiBuf.toString();

Le REPL renverra :

Output
'Hi!'

Ce buffer a été créé à partir d'une chaîne. Voyons ce qui se passe si nous utilisons la fonction toString() sur un buffer qui n'a pas été créé à partir de données de chaîne.

Créons un nouveau buffer vide d'une taille de 10 octets :

  • const tenZeroes = Buffer.alloc(10);

Maintenant, utilisons la méthode toString() :

  • tenZeroes.toString();

Nous verrons le résultat suivant :

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

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

La toString() a un paramètre optionnel, encoding. Nous pouvons utiliser ce paramètre pour modifier l'encodage des données du buffer qui sont renvoyées.

Par exemple, si vous voulez l'encodage hexadecimal pour hiBuf, vous devez entrer ce qui suit à l'invite :

  • hiBuf.toString('hex');

Cette instruction évaluera :

Output
'486921'

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

La méthode toJSON() se comporte différemment. Que le buffer ait été fait à partir d'une chaîne ou non, il renvoie toujours les données sous forme de représentation entière de l'octet.

Réutilisons les buffers hiBuf et tenZeroes pour nous entraîner à utiliser toJSON(). Dans l'invite, saisissez :

  • hiBuf.toJSON();

Le REPL renverra :

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

L'objet JSON a une propriété type qui sera toujours Buffer. C'est ainsi que les programmes peuvent 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 reçues lorsque nous avons tiré les octets individuellement.

Essayons la méthode toJSON() avec tenZeroes :

  • 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 indiqué 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 buffer, voyons comment nous modifions le contenu d'un buffer.

Étape 3 — Modification d'un buffer

Il existe de nombreuses façons de modifier un objet buffer existant. Comme pour la lecture, nous pouvons modifier individuellement les octets du buffer en utilisant la syntaxe du tableau. Nous pouvons également écrire de nouveaux contenus dans un buffer, en remplacement des données existantes.

Commençons par examiner comment nous pouvons modifier les octets individuels d'un buffer. Rappelons notre variable tampon hiBuf, qui contient la chaîne de caractères Hi!. Changeons chaque octet pour qu'elle contienne plutôt Hey.

Dans le REPL, essayons d'abord de remplacer le deuxième élément de hiBuf par e :

  • hiBuf[1] = 'e';

Voyons maintenant ce buffer comme une chaîne de caractères pour confirmer qu'il stocke les bonnes données. Poursuivez en appelant la méthode toString() :

  • hiBuf.toString();

Il sera évalué comme :

Output
'H\u0000!'

Nous avons reçu cette étrange sortie parce que le buffer ne peut accepter qu'une valeur entière. On ne peut pas l'attribuer à la lettre e ; il faut plutôt lui attribuer le nombre dont l'équivalent binaire représente e :

  • hiBuf[1] = 101;

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

  • hiBuf.toString();

Nous obtenons cette sortie dans le REPL :

Output
'He!'

Pour modifier le dernier caractère du buffer, nous devons fixer le troisième élément au nombre entier qui correspond à l'octet pour y :

  • hiBuf[2] = 121;

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

  • hiBuf.toString();

Votre REPL affichera :

Output
'Hey'

Si nous essayons d'écrire un octet qui est en dehors de la plage du buffer, il sera ignoré et le contenu du buffer ne changera pas. Essayons, par exemple, de régler le quatrième élément inexistant du buffer sur o :

  • hiBuf[3] = 111;

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

  • hiBuf.toString();

La sortie est toujours :

Output
'Hey'

Si nous voulons modifier le contenu de l'ensemble du buffer, nous pouvons utiliser la méthode write(). La méthode write() accepte une chaîne de caractères qui remplacera le contenu d'un buffer.

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

  • hiBuf.write('Hi!');

La méthode write() a renvoyé 3 dans le REPL. C'est parce qu'il a écrit trois octets de données. Chaque lettre a la taille d'un octet, puisque ce buffer utilise le codage UTF-8, qui utilise un octet pour chaque caractère. Si le buffer avait utilisé le codage UTF-16, qui comporte un minimum de deux octets par caractère, la fonction write() aurait renvoyé 6.

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

  • hiBuf.toString();

Le REPL produira :

Output
'Hi!'

Cette technique est plus rapide que de devoir modifier chaque élément octet par octet.

Si vous essayez d'écrire plus d'octets que la taille d'un buffer, l'objet buffer n'acceptera que le nombre d'octets qu'il peut recevoir. Pour illustrer cela, créons un buffer qui stocke trois octets :

  • const petBuf = Buffer.alloc(3);

Essayons maintenant d'y écrire Cats :

  • petBuf.write('Cats');

Lorsque l'appel write() est évalué, le REPL renvoie 3, indiquant que seuls trois octets ont été écrits dans le buffer. Confirmez maintenant que le buffer contient les trois premiers octets :

  • petBuf.toString();

Le REPL renvoie :

Output
'Cat'

La fonction write() ajoute les octets dans un ordre séquentiel, de sorte que seuls les trois premiers octets ont été placés dans le buffer.

En revanche, faisons un Buffer qui stocke quatre octets :

  • const petBuf2 = Buffer.alloc(4);

Ecrivez-lui le même contenu :

  • petBuf2.write('Cats');

Ensuite, ajoutez un nouveau contenu qui occupe moins d'espace que le contenu original :

  • petBuf2.write('Hi');

Puisque les buffers s'écrivent séquentiellement en partant de 0, si nous imprimions le contenu du buffer :

  • petBuf2.toString();

Nous verrions :

Output
'Hits'

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

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

Créons deux nouveaux buffers :

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

Les buffers 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 faire passer Nananana de wordsBuf à catchphraseBuf.

Pour copier des données d'un buffer à l'autre, nous utiliserons la méthode copy() sur le buffer qui est la source de l'information. Par conséquent, comme wordsBuf possède les données des chaînes de caractères que nous voulons copier, nous devons copier comme ceci :

  • wordsBuf.copy(catchphraseBuf);

Dans ce cas, le paramètre target est le buffer catchphraseBuf.

Lorsque nous entrons cela dans le REPL, il renvoie 15 indiquant que 15 octets ont été écrits. La chaîne Nananana n'utilise que 8 octets de données, nous savons donc immédiatement que notre copie ne s'est pas déroulée comme prévu. Utilisez la méthode toString() pour voir le contenu de catchphraseBuf :

  • catchphraseBuf.toString();

Le REPL renvoie :

Output
'Banana Nananana!'

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

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

La fonction copy() dispose de quelques paramètres supplémentaires qui nous permettent de personnaliser les données qui sont copiées dans l'autre buffer. Voici une liste de tous les paramètres de cette fonction :

  • target - C'est le seul paramètre requis de copy(). Comme nous l'avons vu lors de notre précédente utilisation, il s'agit du buffer vers lequel nous voulons copier.
  • targetStart - Il s'agit de l'index des octets du buffer cible vers lequel nous devons commencer la copie. Par défaut, il est de 0, ce qui signifie qu'il copie les données à partir du début du buffer.
  • sourceStart - C'est l'index des octets du buffer source où nous devons copier.
  • sourceEnd - C'est l'index des octets dans le buffer source où nous devons arrêter la copie. Par défaut, il s'agit de la longueur du buffer.

Donc, pour copier Nananana de wordsBuf à catchphraseBuf, notre target devrait être catchphraseBuf comme avant. targetStart serait 0, car nous voulons que Nananana apparaisse au début de catchphraseBuf. Le sourceStart devrait être 7, car c'est l'indice où commence Nananana dans wordsBuf. Le sourceEnd continuerait à être la longueur des buffers.

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

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

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

Voyons maintenant le contenu de catchphraseBuf :

  • catchphraseBuf.toString();

Le REPL renvoie :

Output
'Nananana Turtle!'

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

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

  • .exit

Conclusion

Dans ce tutoriel, vous avez appris que les buffers sont des allocations de longueur fixe en mémoire qui stockent des données binaires. Vous avez d'abord créé des buffers en définissant leur taille en mémoire et en les initialisant avec des données pré-existantes. Vous avez ensuite lu les données d'un buffer 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 buffer en changeant ses octets individuels et en utilisant les méthodes write() et copy().

Les buffers vous donnent un bon aperçu de la façon dont les données binaires sont manipulées par Node.js. Maintenant que vous pouvez interagir avec les buffers, vous pouvez observer les différentes façons dont l'encodage des caractères affecte la façon dont les données sont stockées. Par exemple, vous pouvez créer des buffers à partir de données de chaînes qui ne sont pas codées en UTF-8 ou ASCII et observer leur différence de taille. Vous pouvez également choisir un buffer avec UTF-8 et utiliser toString() pour le convertir en d'autres schémas d'encodage.

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

0 Comments

Creative Commons License