One place for hosting & domains

      Buffers

      Utiliser les buffers dans Node.js


      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 :

      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 :

      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 :

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

      Enfin, obtenons le troisième caractère :

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

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

      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 :

      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() :

      Nous verrons le résultat suivant :

      'u0000u0000u0000u0000u0000u0000u0000u0000u0000u0000'
      

      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 :

      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 :

      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 :

      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 :

      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() :

      Il sera évalué comme :

      Output

      'Hu0000!'

      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 :

      Maintenant, lorsque nous appelons la méthode 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 :

      Confirmez en utilisant à nouveau la méthode 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 :

      Nous pouvons confirmer que le buffer est inchangé avec la méthode 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 :

      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() :

      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 :

      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 :

      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 :

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

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

      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 :

      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.



      Source link

      Usando buffers no Node.js


      O autor selecionou a COVID-19 Relief Fund​​​​​ para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Um buffer é um espaço de memória (tipicamente RAM) que armazena dados binários. No Node.js, podemos acessar esses espaços de memória com a classe Buffer integrada. Os buffers armazenam uma sequência de números inteiros, de maneira similar às matrizes em JavaScript. Diferentemente das matrizes, você não pode alterar o tamanho de um buffer após ele ser criado.

      Você pode ter usado buffers implicitamente se você já escreveu algum código Node.js. Por exemplo, quando você lê a partir de um arquivo com o fs.readFile(), os dados que retornam ao callback ou Promessa são um objeto buffer. Além disso, quando as solicitações HTTP são feitas no Node.js, elas retornam os fluxos de dados que estão temporariamente armazenados em um buffer interno quando o cliente não consegue processar todo o fluxo de uma só vez.

      Os buffers são úteis quando você está interagindo com dados binários, geralmente em níveis de rede inferiores. Eles também dão a você a capacidade de fazer a manipulação refinada de dados no Node.js.

      Neste tutorial, você usará o REPL do Node.js para percorrer vários exemplos relacionados a buffers, tais quais a criação de buffers, leitura a partir de buffers, escrever e copiar de buffers e usar buffers para converter dados entre binários e codificados. No final do tutorial, você terá aprendido como usar a classe Buffer para trabalhar com dados binários.

      Pré-requisitos

      Passo 1 — Criando um buffer

      Este primeiro passo mostrará a você as duas maneiras básicas de criar um objeto buffer no Node.js.

      Para decidir qual método usar, você precisa responder a esta pergunta: deseja criar um novo buffer ou extrair um buffer de dados existentes? Se você for armazenar dados em memória que você ainda não recebeu, você vai querer criar um novo buffer. No Node.js, usamos a função alloc() da classe Buffer para fazer isso.

      Vamos abrir o REPL do Node.js para vermos isso. Em seu terminal, digite o comando node:

      Você verá o prompt começando com >.

      A função alloc() recebe o tamanho do buffer como primeiro e único argumento necessário. O tamanho é um número inteiro representando quantos bytes de memória o objeto buffer usará. Por exemplo, se você quisesse criar um buffer que tivesse um tamanho de 1KB (kilobyte), ou seja, o equivalente a 1024 bytes, digitaria isto no console:

      • const firstBuf = Buffer.alloc(1024);

      Para criar um novo buffer, usamos a classe Buffer disponível globalmente, que possui o método alloc(). Ao fornecer 1024 como argumento para alloc(), criamos um buffer que tem 1KB de tamanho.

      Por padrão, quando você inicializa um buffer com o alloc(), o buffer é preenchido com zeros binários como espaço reservado para dados posteriores. No entanto, podemos alterar o valor padrão se quisermos. Se quiséssemos criar um novo buffer com 1s, ao invés de 0s, definiríamos o segundo parâmetro da função alloc()fill.

      Em seu terminal, crie um novo buffer no prompt do REPL que esteja cheio de 1s:

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

      Acabamos de criar um novo objeto buffer que faz referência a um espaço de memória que armazena 1KB de 1s. Embora tenhamos digitado um número inteiro, todos os dados armazenados em um buffer são dados binários.

      Os dados binários podem estar em vários formatos diferentes. Por exemplo, vamos considerar uma sequência binária representando um byte de dados: 01110110. Se essa sequência binária representasse uma string em inglês usando o padrão de codificação ASCII, seria a letra v. No entanto, se nosso computador estivesse processando uma imagem, a sequência binária poderia conter informações sobre a cor de um pixel.

      O computador sabe processá-los de maneira diferente, pois os bytes são codificados de maneira diferente. A codificação do byte é o formato do byte. Um buffer no Node.js utiliza o esquema de codificação UTF-8 por padrão se for inicializado com dados em string. Um byte em UTF-8 representa um número, uma letra (em inglês e em outros idiomas), ou um símbolo. O UTF-8 é um superconjunto de ASCII, o Código em padrão americano para o intercâmbio de informações (do inglês, American Standard Code for Information Interchange). O ASCII pode codificar bytes com letras maiúsculas e minúsculas em inglês, os números 0-9, e alguns outros símbolos como ponto de exclamação (!) ou o E comercial (&).

      Se estivéssemos escrevendo um programa que pudesse funcionar apenas com caracteres ASCII, poderíamos alterar a codificação usada pelo nosso buffer com o terceiro argumento da função alloc()encoding.

      Vamos criar um novo buffer que tenha cinco bytes de comprimento e armazene apenas caracteres ASCII:

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

      O buffer é inicializado com cinco bytes do caractere a, usando a representação ASCII.

      Nota: por padrão, o Node.js suporta as seguintes codificações de caracteres:

      • ASCII, representado como ascii
      • UTF-8, representado como utf-8 ou utf8
      • UTF-16, representado como utf-16le ou utf16le
      • UCS-2, representado como ucs-2 ou ucs2
      • Base64, representado como base64
      • Hexadecimal, representado como hex
      • ISO/IEC 8859-1, representado como latin1 ou binary

      Todos esses valores podem ser usados em funções da classe Buffer que aceitam um parâmetro encoding. Portanto, esses valores são todos válidos para o método alloc().

      Até agora, estivemos criando novos buffers com a função alloc(). Mas, às vezes, pode ser que queiramos criar um buffer a partir de dados que já existem, como uma string ou matriz.

      Para criar um buffer de dados pré-existentes, usamos o método from(). Podemos usar essa função para criar buffers a partir de:

      • Uma matriz de inteiros: os valores inteiros podem estar entre 0 e 255.
      • Um ArrayBuffer: este é um objeto JavaScript que armazena um comprimento fixo de bytes.
      • Uma string.
      • Outro buffer.
      • Outros objetos JavaScript que têm uma propriedade Symbol.toPrimitive. Essa propriedade informa ao JavaScript como converter o objeto em um tipo de dados primitivo: boolean, null, undefined, number, string, ou symbol. Você pode ler mais sobre Símbolos na documentação do JavaScript do Mozilla.

      Vamos ver como podemos criar um buffer a partir de uma string. No prompt do Node.js, digite isto:

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

      Agora, temos um objeto buffer criado a partir da string My name is Paul. Vamos criar um novo buffer a partir de outro buffer que fizemos anteriormente:

      • const asciiCopy = Buffer.from(asciiBuf);

      Agora, criamos um novo buffer asciiCopy que contém os mesmos dados de asciiBuf.

      Agora que experimentamos a criação de buffers, podemos nos aprofundar em exemplos de leitura dos dados deles.

      Passo 2 — Lendo a partir de um buffer

      Há muitas maneiras de acessar dados em um Buffer. Podemos acessar um byte individual em um buffer ou podemos extrair todo o conteúdo.

      Para acessar um byte de um buffer, passamos o índice ou local do byte que queremos. Os buffers armazenam dados sequencialmente como matrizes. Eles também indexam seus dados como matrizes, começando em 0. Podemos usar a notação de uma matriz no objeto buffer para obter um byte individual.

      Vamos ver como isso funciona criando um buffer a partir de uma string no REPL:

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

      Agora, vamos ler o primeiro byte do buffer:

      Assim que você pressionar ENTER, o REPL exibirá:

      Output

      72

      O número inteiro 72 corresponde à representação UTF-8 da letra H.

      Nota: os valores para bytes podem ser números entre 0 e 255. Um byte é uma sequência de 8 bits. Um bit é binário e, portanto, pode ter apenas um de dois valores: 0 ou 1. Se tivermos uma sequência de 8 bits e dois valores possíveis por bit, então teremos um máximo de 2⁸ valores possíveis para um byte. Isso resulta em um máximo de 256 valores. Como começamos a contar a partir de zero, isso significa que nosso número mais alto é 255.

      Vamos fazer o mesmo para o segundo byte. Digite o seguinte no REPL:

      O REPL retorna 105, que representa a letra minúscula i.

      Por fim, vamos obter o terceiro caractere:

      Você verá 33 exibido no REPL, que corresponde a !.

      Vamos tentar recuperar um byte de um índice inválido:

      O REPL retornará:

      Output

      undefined

      Isso é igual a se tentamos acessar um elemento em uma matriz com um índice incorreto.

      Agora que vimos como ler bytes individuais de um buffer, vamos ver nossas opções para recuperar todos os dados armazenados em um buffer de uma só vez. O objeto buffer vem com os métodos toString() e toJSON(), que retornam todo o conteúdo de um buffer em dois diferentes formatos.

      Como o nome sugere, o método toString() converte os bytes do buffer em uma string e a retorna ao usuário. Se usarmos esse método no hiBuf, vamos obter a string Hi!. Vamos tentar!

      No prompt, digite:

      O REPL retornará:

      Output

      'Hi!'

      Esse buffer foi criado a partir de uma string. Vamos ver o que acontece se usarmos o toString() em um buffer que não foi feito a partir de dados em string.

      Vamos criar um novo buffer vazio que tenha um tamanho de 10 bytes:

      • const tenZeroes = Buffer.alloc(10);

      Agora, vamos usar o método toString():

      Veremos o seguinte resultado:

      'u0000u0000u0000u0000u0000u0000u0000u0000u0000u0000'
      

      A string u0000 é o caractere Unicode para o NULL. Ele corresponde ao número 0. Quando os dados do buffer não são codificados como uma string, o método toString() retorna a codificação UTF-8 dos bytes.

      O toString() tem um parâmetro opcional, o enconding. Podemos usar esse parâmetro para alterar a codificação dos dados do buffer que são retornados.

      Por exemplo, se você quisesse a codificação hexadecimal para o hiBuf, você digitaria o seguinte no prompt:

      Essa instrução resultará em:

      Output

      '486921'

      486921 é a representação hexadecimal para os bytes que representam a string Hi!. No Node.js, quando os usuários querem converter a codificação de dados de uma para outra, eles geralmente colocam a string em um buffer e chamam toString() com sua codificação desejada.

      O método toJSON() comporta-se de maneira diferente. Independentemente de se ter sido feito a partir de uma string ou não, ele sempre retorna os dados com a representação de número inteiro do byte.

      Vamos usar novamente os buffers hiBuf e tenZeroes para praticar o uso toJSON(). No prompt, digite:

      O REPL retornará:

      Output

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

      O objeto JSON tem uma propriedade type (tipo) que sempre será Buffer. Isso acontece para que os programas possam distinguir esses objetos JSON de outros objetos JSON.

      A propriedade data contém uma matriz da representação de inteiros dos bytes. Você pode ter notado que 72, 105, e 33 correspondem aos valores que recebemos quando acessamos os bytes individualmente.

      Vamos tentar o método toJSON() com tenZeroes:

      No REPL, você verá o seguinte:

      Output

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

      O type é o mesmo que você viu anteriormente. No entanto, os dados agora são uma matriz com dez zeros.

      Agora que abordamos as principais formas de ler a partir de um buffer, vamos ver como modificar o conteúdo de um buffer.

      Passo 3 — Modificando um buffer

      Há muitas maneiras de modificar um objeto buffer existente. De maneira semelhante à leitura, podemos modificar bytes de buffer individualmente usando a sintaxe de matriz. Também podemos escrever novos conteúdos para um buffer, substituindo os dados existentes.

      Vamos começar analisando como podemos alterar os bytes individualmente de um buffer. Lembre-se de nossa variável buffer hiBuf, que contém a string Hi! Vamos alterar cada byte para que, ao invés disso, ela contenha Hey.

      No REPL, vamos primeiro tentar definir o segundo elemento do hiBuf para e:

      Agora, vamos ver este buffer como uma string para confirmar se ele está armazenando os dados corretos. Em seguida, chame o método toString():

      Ele será avaliado como:

      Output

      'Hu0000!'

      Recebemos esse resultado estranho porque o buffer pode aceitar apenas um valor de número inteiro. Não podemos atribuir-lhe a letra e; ao invés disso, precisamos atribuir-lhe o número cujo equivalente binário represente e:

      Agora, quando chamamos o método toString():

      Obtemos este resultado no REPL:

      Output

      'He!'

      Para alterar o último caractere no buffer, precisamos definir o terceiro elemento como o número inteiro que corresponda ao byte para y:

      Vamos confirmar usando o método toString() novamente:

      Seu REPL exibirá:

      Output

      'Hey'

      Se tentarmos escrever um byte que esteja fora do alcance do buffer, ele será ignorado e o conteúdo do buffer não mudará. Por exemplo, vamos tentar definir o quarto elemento não existente do buffer como o:

      Podemos confirmar que o buffer permanece inalterado com o método toString():

      O resultado ainda é:

      Output

      'Hey'

      Se quiséssemos alterar o conteúdo de todo o buffer, poderíamos usar o método write(). O método write() aceita uma string que substituirá o conteúdo de um buffer.

      Vamos usar o método write() para alterar o conteúdo do hiBuf de volta para Hi! Em seu shell do Node.js, digite o seguinte comando no prompt:

      O método write() retornou 3 no REPL. Isso aconteceu porque ele escreveu três bytes de dados. Cada letra tem o tamanho de um byte, uma vez que este buffer utiliza a codificação UTF-8, a qual utiliza um byte para cada caractere. Se o buffer tivesse usado a codificação UTF-16, que tem um mínimo de dois bytes por caractere, então a função write() teria retornado 6.

      Agora, verifique o conteúdo do buffer usando toString():

      O REPL produzirá:

      Output

      'Hi!'

      Isso é mais rápido do que ter que alterar cada elemento byte por byte.

      Se você tentar escrever mais bytes do que o tamanho de um buffer, o objeto buffer aceitará apenas os bytes que se encaixam. Para ilustrar, vamos criar um buffer que armazena três bytes:

      • const petBuf = Buffer.alloc(3);

      Agora, vamos tentar escrever Cats nele:

      Quando a chamada write() é avaliada, o REPL retorna 3, indicando que apenas três bytes foram escritos no buffer. Agora, confirme se o buffer contém os três primeiros bytes:

      O REPL retorna:

      Output

      'Cat'

      A função write() adiciona os bytes em ordem sequencial, de modo que apenas os três primeiros bytes foram colocados no buffer.

      De maneira contrária, vamos criar um Buffer que armazene quatro bytes:

      • const petBuf2 = Buffer.alloc(4);

      Escreva o mesmo conteúdo nele:

      Então, adicione um novo conteúdo que ocupe menos espaço do que o conteúdo original:

      Como os buffers escrevem sequencialmente, começando a partir de 0, se imprimirmos o conteúdo do buffer:

      Receberíamos isto como resultado:

      Output

      'Hits'

      Os dois primeiros caracteres foram substituídos, mas o resto do buffer permaneceu intacto.

      Às vezes, os dados que queremos em nosso buffer já existente não estão em uma string, mas sim residindo em outro objeto buffer. Nestes casos, podemos usar a função copy() para modificar o que nosso buffer está armazenando.

      Vamos criar dois novos buffers:

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

      Os buffers wordsBuf e catchphraseBuf contêm ambos dados em string. Queremos modificar o catchphraseBuf de modo que ele armazene Nananana Turtle! ao invés de Not sure Turtle!. Usaremos o copy() para transferir o Nananana de wordsBuf para wordsBuf.

      Para copiar dados de um buffer para outro, usaremos o método copy() no buffer que é a origem das informações. Portanto, como o wordsBuf possui os dados em string que queremos copiar, precisamos copiar desta forma:

      • wordsBuf.copy(catchphraseBuf);

      O parâmetro target (alvo) neste caso é o buffer catchphraseBuf.

      Quando digitamos isso no REPL, ele retorna 15, indicando que 15 bytes foram escritos. A string o Nananana utiliza apenas 8 bytes de dados, de modo que sabemos imediatamente que nossa cópia não ocorreu como previsto. Use o método toString() para ver o conteúdo de catchphraseBuf:

      • catchphraseBuf.toString();

      O REPL retorna:

      Output

      'Banana Nananana!'

      Por padrão, o copy() pegou todo o conteúdo de wordsBuf e o colocou em catchphraseBuf. Precisamos ser mais seletivos para atingir nosso objetivo de copiar apenas o Nananana. Vamos reescrever o conteúdo original de catchphraseBuf antes de continuar:

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

      A função copy() tem alguns outros parâmetros que nos permitem personalizar quais dados serão copiados para outro buffer. Aqui está uma lista de todos os parâmetros dessa função:

      • target – Este é o único parâmetro necessário de copy(). Como vimos em nosso uso anterior, ele é o buffer para o qual queremos copiar.
      • targetStart – este é o índice dos bytes no buffer de destino para onde devemos começar a copiar. Por padrão é 0, ou seja, ele copia dados começando no início de um buffer.
      • sourceStart – este é o índice dos bytes no buffer de origem de onde devemos copiar.
      • sourceEnd – este é o índice dos bytes no buffer de origem onde devemos parar de copiar. Por padrão, ele é o comprimento do buffer.

      Assim, para copiar o Nananana de wordsBuf para catchphraseBuf, nosso target deve ser catchphraseBuf assim como anteriormente. O targetStart deve ser 0, já que queremos que o Nananana apareça no início de catchphraseBuf. O sourceStart deve ser 7, pois é o índice onde o Nananana começa em wordsBuf. O sourceEnd continuaria sendo o comprimento dos buffers.

      No prompt do REPL, copie o conteúdo de wordsBuf desta forma:

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

      O REPL confirma que 8 bytes foram escritos. Observe como o wordsBuf.length é usado como o valor para o parâmetro sourceEnd. Assim como nas matrizes, a propriedade length nos dá o tamanho do buffer.

      Agora, vamos ver o conteúdo de catchphraseBuf:

      • catchphraseBuf.toString();

      O REPL retorna:

      Output

      'Nananana Turtle!'

      Success! Fomos capazes de modificar os dados de catchphraseBuf copiando o conteúdo de wordsBuf.

      Você pode sair do REPL do Node.js caso queira fazer isso. Observe que todas as variáveis que foram criadas não estarão mais disponíveis quando você o fizer:

      Conclusão

      Neste tutorial, você aprendeu que os buffers são alocações de comprimento fixo em memória que armazenam dados binários. Primeiro, você criou buffers definindo seu tamanho em memória e os inicializando com dados pré-existentes. Em seguida, você leu dados de um buffer examinando seus bytes individuais e utilizando os métodos toString() e toJSON(). Por fim, você modificou os dados armazenados por um buffer modificando seus bytes individuais e usando os métodos write() e copy().

      Os buffers te fazem compreender como os dados binários são manipulados pelo Node.js. Agora que você consegue interagir com buffers, observe as diferentes maneiras como a codificação de caracteres afeta como os dados são armazenados. Por exemplo, você pode criar buffers a partir de dados em string que não estejam codificados em UTF-8 ou ASCII e observar suas diferenças de tamanho. Você também pode usar um buffer com o UTF-8 e usar toString() para convertê-lo para outros esquemas de codificação.

      Para aprender sobre buffers no Node.js, você pode ler parte da documentação do Node.js que diz respeito ao objeto Buffer. Se quiser continuar aprendendo sobre o Node.js, você pode retornar para a série Como programar em Node.js, ou pesquisar por projetos de programação e configurações em nossa página de tópicos do Node.



      Source link

      Using Buffers in Node.js


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      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.

      You may have used buffers implicitly if you wrote Node.js code already. For example, when you read from a file with fs.readFile(), the data returned to the callback or Promise is a buffer object. Additionally, when HTTP requests are made in Node.js, they return data streams that are temporarily stored in an internal buffer when the client cannot process the stream all at once.

      Buffers are useful when you’re interacting with binary data, usually at lower networking levels. They also equip you with the ability to do fine-grained data manipulation in Node.js.

      In this tutorial, you will use the Node.js REPL to run through various examples of buffers, such as creating buffers, reading from buffers, writing to and copying from buffers, and using buffers to convert between binary and encoded data. By the end of the tutorial, you’ll have learned how to use the Buffer class to work with binary data.

      Prerequisites

      Step 1 — Creating a Buffer

      This first step will show you the two primary ways to create a buffer object in Node.js.

      To decide what method to use, you need to answer this question: Do you want to create a new buffer or extract a buffer from existing data? If you are going to store data in memory that you have yet to receive, you’ll want to create a new buffer. In Node.js we use the alloc() function of the Buffer class to do this.

      Let’s open the Node.js REPL to see for ourselves. In your terminal, enter the node command:

      You will see the prompt begin with >.

      The alloc() function takes the size of the buffer as its first and only required argument. The size is an integer representing how many bytes of memory the buffer object will use. For example, if we wanted to create a buffer that was 1KB (kilobyte) large, equivalent to 1024 bytes, we would enter this in the console:

      • const firstBuf = Buffer.alloc(1024);

      To create a new buffer, we used the globally available Buffer class, which has the alloc() method. By providing 1024 as the argument for alloc(), we created a buffer that’s 1KB large.

      By default, when you initialize a buffer with alloc(), the buffer is filled with binary zeroes as a placeholder for later data. However, we can change the default value if we’d like to. If we wanted to create a new buffer with 1s instead of 0s, we would set the alloc() function’s second parameter—fill.

      In your terminal, create a new buffer at the REPL prompt that’s filled with 1s:

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

      We just created a new buffer object that references a space in memory that stores 1KB of 1s. Although we entered an integer, all data stored in a buffer is binary data.

      Binary data can come in many different formats. For example, let’s consider a binary sequence representing a byte of data: 01110110. If this binary sequence represented a string in English using the ASCII encoding standard, it would be the letter v. However, if our computer was processing an image, that binary sequence could contain information about the color of a pixel.

      The computer knows to process them differently because the bytes are encoded differently. Byte encoding is the format of the byte. A buffer in Node.js uses the UTF-8 encoding scheme by default if it’s initialized with string data. A byte in UTF-8 represents a number, a letter (in English and in other languages), or a symbol. UTF-8 is a superset of ASCII, the American Standard Code for Information Interchange. ASCII can encode bytes with uppercase and lowercase English letters, the numbers 0-9, and a few other symbols like the exclamation mark (!) or the ampersand sign (&).

      If we were writing a program that could only work with ASCII characters, we could change the encoding used by our buffer with the alloc() function’s third argument—encoding.

      Let’s create a new buffer that’s five bytes long and stores only ASCII characters:

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

      The buffer is initialized with five bytes of the character a, using the ASCII representation.

      Note: By default, Node.js supports the following character encodings:

      • ASCII, represented as ascii
      • UTF-8, represented as utf-8 or utf8
      • UTF-16, represented as utf-16le or utf16le
      • UCS-2, represented as ucs-2 or ucs2
      • Base64, represented as base64
      • Hexadecimal, represented as hex
      • ISO/IEC 8859-1, represented as latin1 or binary

      All of these values can be used in Buffer class functions that accept an encoding parameter. Therefore, these values are all valid for the alloc() method.

      So far we’ve been creating new buffers with the alloc() function. But sometimes we may want to create a buffer from data that already exists, like a string or array.

      To create a buffer from pre-existing data, we use the from() method. We can use that function to create buffers from:

      • An array of integers: The integer values can be between 0 and 255.
      • An ArrayBuffer: This is a JavaScript object that stores a fixed length of bytes.
      • A string.
      • Another buffer.
      • Other JavaScript objects that have a Symbol.toPrimitive property. That property tells JavaScript how to convert the object to a primitive data type: boolean, null, undefined, number, string, or symbol. You can read more about Symbols at Mozilla’s JavaScript documentation.

      Let’s see how we can create a buffer from a string. In the Node.js prompt, enter this:

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

      We now have a buffer object created from the string My name is Paul. Let’s create a new buffer from another buffer we made earlier:

      • const asciiCopy = Buffer.from(asciiBuf);

      We’ve now created a new buffer asciiCopy that contains the same data as asciiBuf.

      Now that we have experienced creating buffers, we can dive into examples of reading their data.

      Step 2 — Reading from a Buffer

      There are many ways to access data in a Buffer. We can access an individual byte in a buffer or we can extract the entire contents.

      To access one byte of a buffer, we pass the index or location of the byte we want. Buffers store data sequentially like arrays. They also index their data like arrays, starting at 0. We can use array notation on the buffer object to get an individual byte.

      Let’s see how this looks by creating a buffer from a string in the REPL:

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

      Now let’s read the first byte of the buffer:

      As you press ENTER, the REPL will display:

      Output

      72

      The integer 72 corresponds the UTF-8 representation for the letter H.

      Note: The values for bytes can be numbers between 0 and 255. A byte is a sequence of 8 bits. A bit is binary, and therefore can only have one of two values: 0 or 1. If we have a sequence of 8 bits and two possible values per bit, then we have a maximum of 2⁸ possible values for a byte. That works out to a maximum of 256 values. Since we start counting from zero, that means our highest number is 255.

      Let’s do the same for the second byte. Enter the following in the REPL:

      The REPL returns 105, which represents the lowercase i.

      Finally, let’s get the third character:

      You will see 33 displayed in the REPL, which corresponds to !.

      Let’s try to retrieve a byte from an invalid index:

      The REPL will return:

      Output

      undefined

      This is just like if we tried to access an element in an array with an incorrect index.

      Now that we’ve seen how to read individual bytes of a buffer, let’s see our options for retrieving all the data stored in a buffer at once. The buffer object comes with the toString() and the toJSON() methods, which return the entire contents of a buffer in two different formats.

      As its name suggests, the toString() method converts the bytes of the buffer into a string and returns it to the user. If we use this method on hiBuf, we will get the string Hi!. Let’s try it!

      In the prompt, enter:

      The REPL will return:

      Output

      'Hi!'

      That buffer was created from a string. Let’s see what happens if we use the toString() on a buffer that was not made from string data.

      Let’s create a new, empty buffer that’s 10 bytes large:

      • const tenZeroes = Buffer.alloc(10);

      Now, let’s use the toString() method:

      We will see the following result:

      'u0000u0000u0000u0000u0000u0000u0000u0000u0000u0000'
      

      The string u0000 is the Unicode character for NULL. It corresponds to the number 0. When the buffer’s data is not encoded as a string, the toString() method returns the UTF-8 encoding of the bytes.

      The toString() has an optional parameter, encoding. We can use this parameter to change the encoding of the buffer data that’s returned.

      For example, if you wanted the hexadecimal encoding for hiBuf you would enter the following at the prompt:

      That statement will evaluate to:

      Output

      '486921'

      486921 is the hexadecimal representation for the bytes that represent the string Hi!. In Node.js, when users want to convert the encoding of data from one form to another, they usually put the string in a buffer and call toString() with their desired encoding.

      The toJSON() method behaves differently. Regardless of whether the buffer was made from a string or not, it always returns the data as the integer representation of the byte.

      Let’s re-use the hiBuf and tenZeroes buffers to practice using toJSON(). At the prompt, enter:

      The REPL will return:

      Output

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

      The JSON object has a type property that will always be Buffer. That’s so programs can distinguish these JSON object from other JSON objects.

      The data property contains an array of the integer representation of the bytes. You may have noticed that 72, 105, and 33 correspond to the values we received when we individually pulled the bytes.

      Let’s try the toJSON() method with tenZeroes:

      In the REPL you will see the following:

      Output

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

      The type is the same as noted before. However, the data is now an array with ten zeroes.

      Now that we’ve covered the main ways to read from a buffer, let’s look at how we modify a buffer’s contents.

      Step 3 — Modifying a Buffer

      There are many ways we can modify an existing buffer object. Similar to reading, we can modify buffer bytes individually using the array syntax. We can also write new contents to a buffer, replacing the existing data.

      Let’s begin by looking at how we can change individual bytes of a buffer. Recall our buffer variable hiBuf, which contains the string Hi!. Let’s change each byte so that it contains Hey instead.

      In the REPL, let’s first try setting the second element of hiBuf to e:

      Now, let’s see this buffer as a string to confirm it’s storing the right data. Follow up by calling the toString() method:

      It will be evaluated as:

      Output

      'Hu0000!'

      We received that strange output because the buffer can only accept an integer value. We can’t assign it to the letter e; rather, we have to assign it the number whose binary equivalent represents e:

      Now when we call the toString() method:

      We get this output in the REPL:

      Output

      'He!'

      To change the last character in the buffer, we need to set the third element to the integer that corresponds to the byte for y:

      Let’s confirm by using the toString() method once again:

      Your REPL will display:

      Output

      'Hey'

      If we try to write a byte that’s outside the range of the buffer, it will be ignored and the contents of the buffer won’t change. For example, let’s try to set the non-existent fourth element of the buffer to o:

      We can confirm that the buffer is unchanged with the toString() method:

      The output is still:

      Output

      'Hey'

      If we wanted to change the contents of the entire buffer, we can use the write() method. The write() method accepts a string that will replace the contents of a buffer.

      Let’s use the write() method to change the contents of hiBuf back to Hi!. In your Node.js shell, type the following command at the prompt:

      The write() method returned 3 in the REPL. This is because it wrote three bytes of data. Each letter has one byte in size, since this buffer uses UTF-8 encoding, which uses a byte for each character. If the buffer used UTF-16 encoding, which has a minimum of two bytes per character, then the write() function would have returned 6.

      Now verify the contents of the buffer by using toString():

      The REPL will produce:

      Output

      'Hi!'

      This is quicker than having to change each element byte-by-byte.

      If you try to write more bytes than a buffer’s size, the buffer object will only accept what bytes fit. To illustrate, let’s create a buffer that stores three bytes:

      • const petBuf = Buffer.alloc(3);

      Now let’s attempt to write Cats to it:

      When the write() call is evaluated, the REPL returns 3 indicating only three bytes were written to the buffer. Now confirm that the buffer contains the first three bytes:

      The REPL returns:

      Output

      'Cat'

      The write() function adds the bytes in sequential order, so only the first three bytes were placed in the buffer.

      By contrast, let’s make a Buffer that stores four bytes:

      • const petBuf2 = Buffer.alloc(4);

      Write the same contents to it:

      Then add some new content that occupies less space than the original content:

      Since buffers write sequentially, starting from 0, if we print the buffer’s contents:

      We’d be greeted with:

      Output

      'Hits'

      The first two characters are overwritten, but the rest of the buffer is untouched.

      Sometimes the data we want in our pre-existing buffer is not in a string but resides in another buffer object. In these cases, we can use the copy() function to modify what our buffer is storing.

      Let’s create two new buffers:

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

      The wordsBuf and catchphraseBuf buffers both contain string data. We want to modify catchphraseBuf so that it stores Nananana Turtle! instead of Not sure Turtle!. We’ll use copy() to get Nananana from wordsBuf to catchphraseBuf.

      To copy data from one buffer to the other, we’ll use the copy() method on the buffer that’s the source of the information. Therefore, as wordsBuf has the string data we want to copy, we need to copy like this:

      • wordsBuf.copy(catchphraseBuf);

      The target parameter in this case is the catchphraseBuf buffer.

      When we enter that into the REPL, it returns 15 indicating that 15 bytes were written. The string Nananana only uses 8 bytes of data, so we immediately know that our copy did not go as intended. Use the toString() method to see the contents of catchphraseBuf:

      • catchphraseBuf.toString();

      The REPL returns:

      Output

      'Banana Nananana!'

      By default, copy() took the entire contents of wordsBuf and placed it into catchphraseBuf. We need to be more selective for our goal and only copy Nananana. Let’s re-write the original contents of catchphraseBuf before continuing:

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

      The copy() function has a few more parameters that allow us to customize what data is copied to the other buffer. Here’s a list of all the parameters of this function:

      • target – This is the only required parameter of copy(). As we’ve seen from our previous usage, it is the buffer we want to copy to.
      • targetStart – This is the index of the bytes in the target buffer where we should begin copying to. By default it’s 0, meaning it copies data starting at the beginning of the buffer.
      • sourceStart – This is the index of the bytes in the source buffer where we should copy from.
      • sourceEnd – This is the index of the bytes in the source buffer where we should stop copying. By default, it’s the length of the buffer.

      So, to copy Nananana from wordsBuf into catchphraseBuf, our target should be catchphraseBuf like before. The targetStart would be 0 as we want Nananana to appear at the beginning of catchphraseBuf. The sourceStart should be 7 as that’s the index where Nananana begins in wordsBuf. The sourceEnd would continue to be the length of the buffers.

      At the REPL prompt, copy the contents of wordsBuf like this:

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

      The REPL confirms that 8 bytes have been written. Note how wordsBuf.length is used as the value for the sourceEnd parameter. Like arrays, the length property gives us the size of the buffer.

      Now let’s see the contents of catchphraseBuf:

      • catchphraseBuf.toString();

      The REPL returns:

      Output

      'Nananana Turtle!'

      Success! We were able to modify the data of catchphraseBuf by copying the contents of wordsBuf.

      You can exit the Node.js REPL if you would like to do so. Note that all the variables that were created will no longer be available when you do:

      Conclusion

      In this tutorial, you learned that buffers are fixed-length allocations in memory that store binary data. You first created buffers by defining their size in memory and by initializing them with pre-existing data. You then read data from a buffer by examining their individual bytes and by using the toString() and toJSON() methods. Finally, you modified the data stored by a buffer by changing its individual bytes and by using the write() and copy() methods.

      Buffers give you great insight into how binary data is manipulated by Node.js. Now that you can interact with buffers, you can observe the different ways character encoding affect how data is stored. For example, you can create buffers from string data that are not UTF-8 or ASCII encoding and observe their difference in size. You can also take a buffer with UTF-8 and use toString() to convert it to other encoding schemes.

      To learn about buffers in Node.js, you can read the Node.js documentation on the Buffer object. If you’d like to continue learning Node.js, you can return to the How To Code in Node.js series, or browse programming projects and setups on our Node topic page.



      Source link