Contactez-nous

Création et manipulation de buffers

Apprenez les differentes methodes pour creer des Buffers Node.js (from, alloc, allocUnsafe) et les manipuler : lecture/ecriture d'octets, slicing, copie, remplissage.

Les differentes facons de creer un Buffer

La première étape pour travailler avec des données binaires est de créer un objet Buffer. Node.js offre plusieurs méthodes statiques sur la classe `Buffer` pour répondre à différents besoins de création, que ce soit à partir de données existantes ou en allouant de la nouvelle mémoire.

1. `Buffer.from()` : Créer à partir de données existantes

C'est la méthode la plus polyvalente pour créer un Buffer basé sur des données que vous possédez déjà :

  • Buffer.from(string, [encoding]) : Convertit une chaîne de caractères en Buffer selon l'encodage spécifié (par défaut `'utf8'`). Très courant.
  • Buffer.from(array) : Crée un Buffer à partir d'un tableau d'octets (nombres entre 0 et 255).
  • Buffer.from(buffer) : Crée une copie d'un Buffer existant. Les deux Buffers auront des zones mémoires distinctes.
  • Buffer.from(arrayBuffer[, byteOffset[, length]]) : Crée une vue Buffer qui partage la mémoire sous-jacente d'un `ArrayBuffer` JavaScript standard (ou `SharedArrayBuffer`). Utile pour interagir avec des API Web comme Fetch ou WebSockets.
const bufFromString = Buffer.from('Bonjour', 'utf8');
console.log(bufFromString); // 

const bufFromArray = Buffer.from([66, 111, 111]); // ASCII pour 'foo'
console.log(bufFromArray.toString()); // 'foo'

const originalBuf = Buffer.from('test');
const bufFromBuf = Buffer.from(originalBuf);
originalBuf[0] = 117; // 't' -> 'u'
console.log(originalBuf.toString()); // 'uest'
console.log(bufFromBuf.toString()); // 'test' (la copie n'est pas affectée)

const arrBuf = new ArrayBuffer(4);
const uint8Arr = new Uint8Array(arrBuf);
uint8Arr[0] = 1; uint8Arr[1] = 2;
const bufFromArrayBuf = Buffer.from(arrBuf, 1, 2); // Vue sur les octets 2 et 3 (index 1 et 2)
console.log(bufFromArrayBuf); //  (si uint8Arr[2] et [3] sont 0)

2. `Buffer.alloc(size, [fill], [encoding])` : Allouer une mémoire sûre et initialisée

Cette méthode alloue un nouveau Buffer de la taille (`size`) spécifiée en octets. Point crucial : la mémoire allouée est initialisée (remplie de zéros par défaut, ou avec la valeur `fill` si fournie). C'est la méthode la plus sûre et recommandée pour créer un Buffer vide ou pré-rempli, car elle garantit qu'aucune donnée résiduelle sensible ne sera exposée.

// Buffer de 8 octets, rempli de zéros
const safeBuf = Buffer.alloc(8);
console.log(safeBuf); // 

// Buffer de 5 octets, rempli avec la valeur décimale 1 (0x01)
const filledBuf = Buffer.alloc(5, 1);
console.log(filledBuf); // 

// Buffer de 6 octets, rempli avec la répétition de 'abc' (encodé en UTF-8)
const patternBuf = Buffer.alloc(6, 'abc', 'utf8');
console.log(patternBuf); //  ('a'=61, 'b'=62, 'c'=63)

3. `Buffer.allocUnsafe(size)` : Allouer une mémoire non initialisée (Rapide mais Dangereux)

Similaire à `alloc`, mais cette méthode n'initialise pas la mémoire allouée. Elle est plus rapide car elle évite l'étape de remplissage, mais le Buffer résultant peut contenir d'anciennes données provenant d'utilisations précédentes de cette zone mémoire. C'est une source potentielle de fuites de données sensibles.

Règle d'or : Si vous utilisez `allocUnsafe`, vous devez impérativement remplir (écraser) la totalité du Buffer immédiatement après sa création en utilisant des méthodes comme `buf.fill()`, `buf.write()`, ou `buf.copy()` avant de le lire ou de le transmettre.

// ATTENTION : Utilisation risquée !
const unsafeBuf = Buffer.allocUnsafe(10);
console.log('unsafeBuf (contenu initial inconnu):', unsafeBuf);

// Remplissage OBLIGATOIRE après allocUnsafe
unsafeBuf.fill(0);
console.log('unsafeBuf (après remplissage):', unsafeBuf);

Privilégiez toujours `Buffer.alloc()` sauf si vous avez un besoin critique de performance dans une boucle très serrée et que vous comprenez parfaitement les implications de sécurité et garantissez le remplissage immédiat.

Lire et ecrire dans un Buffer

Une fois un Buffer créé, vous pouvez interagir avec ses octets :

  • Accès direct par index : Comme un tableau, vous pouvez lire ou écrire la valeur d'un octet spécifique (nombre entre 0 et 255) en utilisant son index (commençant à 0).
  • `buf.write(string, [offset], [length], [encoding])` : Ecrit une chaîne de caractères dans le Buffer à partir de la position `offset` (par défaut 0). Vous pouvez spécifier le nombre maximum d'octets à écrire (`length`) et l'encodage (par défaut `'utf8'`). Retourne le nombre d'octets effectivement écrits.
  • `buf.toString([encoding], [start], [end])` : Lit une partie (ou la totalité) du Buffer et la décode en une chaîne de caractères en utilisant l'encodage spécifié (par défaut `'utf8'`).
  • Méthodes de lecture/écriture spécifiques : Le Buffer propose aussi des méthodes pour lire/écrire des types numériques spécifiques (entiers signés/non signés de différentes tailles, flottants) à des offsets précis, en gérant l'endianness (ex: `buf.readInt16LE()`, `buf.writeUInt32BE()`, `buf.readFloatBE()`). Ces méthodes sont essentielles pour travailler avec des formats binaires structurés.

Exemple de lecture/écriture :

const buf = Buffer.alloc(12);

// Ecrire une chaîne
let bytesWritten = buf.write('Node', 0, 4, 'utf8');
console.log(`Octets écrits: ${bytesWritten}, Buffer: ${buf}`); // 4, 

bytesWritten = buf.write('.js', 4, 3, 'utf8');
console.log(`Octets écrits: ${bytesWritten}, Buffer: ${buf}`); // 3, 

// Lire le contenu
console.log('Contenu complet:', buf.toString('utf8')); // 'Node.js'
console.log('Partie du contenu:', buf.toString('utf8', 0, 4)); // 'Node'

// Accès par index
console.log('Octet à l\'index 5:', buf[5]); // 106 (code ASCII de 'j')
buf[5] = 74; // Remplace 'j' par 'J'
console.log('Contenu après modif index:', buf.toString('utf8')); // 'Node.Js'

// Ecrire un entier non signé de 16 bits (2 octets) en Big Endian à l'offset 8
buf.writeUInt16BE(258, 8); // 258 = 0x0102
console.log(`Buffer après écriture nombre: ${buf}`); // 

// Lire cet entier
const numberRead = buf.readUInt16BE(8);
console.log('Nombre lu:', numberRead); // 258

Sous-sections : Slicing vs Copie

Il est crucial de comprendre la différence entre créer une "vue" (slice) et créer une "copie" d'un Buffer :

  • `buf.slice([start], [end])` : Crée un nouveau Buffer qui pointe vers la même zone mémoire que le Buffer original, mais décalé et/ou tronqué. Modifier le slice modifie l'original, et vice-versa. C'est très rapide car aucune donnée n'est copiée, mais cela peut entraîner des effets de bord inattendus si l'on n'est pas vigilant.
  • `Buffer.from(buf)` ou `buf.copy(targetBuffer, ...)` : Crée une copie complète des données dans une nouvelle zone mémoire indépendante. Modifier la copie n'affecte pas l'original. C'est plus coûteux en temps et en mémoire car les données sont dupliquées, mais c'est plus sûr si vous avez besoin de garantir l'isolation des données.

Exemple illustrant la différence :

const original = Buffer.from([1, 2, 3, 4, 5]);

// Slicing (partage de mémoire)
const slice = original.slice(1, 4); // Pointe vers les octets aux index 1, 2, 3 de l'original
console.log('Original:', original); // 
console.log('Slice:', slice);       // 

slice[0] = 99; // Modifier le premier octet du slice (qui est le 2ème de l'original)
console.log('Slice modifié:', slice); //  (99 en hexa = 63)
console.log('Original affecté:', original); //  (le 2ème octet a changé)

// Copie (mémoire indépendante)
const copy = Buffer.alloc(original.length);
original.copy(copy);
console.log('Copie:', copy); // 

copy[0] = 11; // Modifier la copie
console.log('Copie modifiée:', copy); // 
console.log('Original non affecté:', original); //  (reste inchangé)

Choisissez `slice` pour la performance lorsque vous savez que le partage de mémoire est sûr, et préférez une copie (`Buffer.from` ou `copy`) lorsque vous avez besoin d'isoler les données.

Autres manipulations utiles

D'autres méthodes pratiques existent :

  • `buf.fill(value, [offset], [end], [encoding])` : Remplit le Buffer (ou une partie) avec une valeur spécifiée. Essentiel après `allocUnsafe`.
  • `Buffer.concat(list, [totalLength])` : Concatène un tableau (`list`) de Buffers en un unique nouveau Buffer. C'est plus efficace que de concaténer manuellement. Si `totalLength` n'est pas fourni, il est calculé, ce qui a un coût ; le fournir si connu améliore la performance.
  • `buf.equals(otherBuffer)` : Compare le contenu de deux Buffers octet par octet. Retourne `true` s'ils sont identiques, `false` sinon.
  • `Buffer.compare(buf1, buf2)` : Compare deux Buffers. Retourne `-1`, `0`, ou `1`, utile pour le tri lexicographique des Buffers.
  • `buf.indexOf(value, [byteOffset], [encoding])` / `buf.includes(...)` : Recherche une sous-séquence (chaîne, Buffer ou nombre) dans le Buffer.
  • `buf.length` : Propriété (pas une méthode) qui retourne la taille du Buffer en octets.
const bufA = Buffer.from('abc');
const bufB = Buffer.from('def');
const bufC = Buffer.from('abcdef');

// Concaténation
const combined = Buffer.concat([bufA, bufB]);
console.log('Concat:', combined.toString()); // 'abcdef'

// Comparaison
console.log('bufA equals bufB:', bufA.equals(bufB)); // false
console.log('combined equals bufC:', combined.equals(bufC)); // true
console.log('Buffer.compare(bufA, bufB):', Buffer.compare(bufA, bufB)); // -1 (bufA vient avant bufB)

// Remplissage
const fillBuf = Buffer.alloc(10);
fillBuf.fill('X', 2, 8); // Remplit avec 'X' de l'index 2 à 7
console.log('Filled Buf:', fillBuf); // 

Maîtriser ces méthodes de création et de manipulation est fondamental pour travailler efficacement avec les données binaires en Node.js, que ce soit pour interagir avec le système de fichiers, le réseau, ou des formats de données spécifiques.