L’autore ha selezionato il Fondo di Soccorso COVID-19 per ricevere una donazione come parte del programma Scrivi per le Donazioni.
Introduzione
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.
Potresti aver utilizzato i buffer implicitamente se hai già scritto codice Node.js. Ad esempio, quando leggi da un file con fs.readFile()
, i dati restituiti alla funzione di richiamo o alla Promise sono un oggetto buffer . Inoltre, quando vengono effettuate richieste HTTP in Node.js, restituiscono flussi di dati che vengono temporaneamente memorizzati in un buffer interno quando il client non può elaborare il flusso tutto in una volta.
I buffer sono utili quando interagisci con dati binari, di solito a livelli di rete inferiori. Ti forniscono anche la capacità di manipolare i dati in modo dettagliato in Node.js.
In questo tutorial, utilizzerai il REPL di Node.js per eseguire vari esempi di buffer, come la creazione di buffer, la lettura da buffer, la scrittura su buffer, la copia da buffer e l’utilizzo dei buffer per convertire tra dati binari e codificati. Alla fine del tutorial, avrai imparato come utilizzare la classe Buffer per lavorare con dati binari.
Prerequisiti
- Avrai bisogno di Node.js installato sulla tua macchina di sviluppo. Questo tutorial utilizza la versione 10.19.0. Per installarlo su macOS o Ubuntu 18.04, segui i passaggi in Come installare Node.js e creare un ambiente di sviluppo locale su macOS o nella sezione Installazione utilizzando un PPA di Come installare Node.js su Ubuntu 18.04.
- In questo tutorial, interagirai con i buffer nel REPL di Node.js (Read-Evaluate-Print-Loop). Se desideri un ripasso su come utilizzare efficacemente il REPL di Node.js, puoi leggere la nostra guida su Come utilizzare il REPL di Node.js.
- Per questo articolo ci aspettiamo che l’utente sia a suo agio con il JavaScript di base e i suoi tipi di dati. Puoi apprendere questi fondamenti con la nostra serie Come Codificare in JavaScript.
Passaggio 1 — Creare un Buffer
Questo primo passaggio ti mostrerà i due modi principali per creare un oggetto buffer in Node.js.
Per decidere quale metodo utilizzare, devi rispondere a questa domanda: vuoi creare un nuovo buffer o estrarre un buffer dai dati esistenti? Se devi memorizzare dati in memoria che devi ancora ricevere, vorrai creare un nuovo buffer. In Node.js utilizziamo la funzione alloc()
della Buffer
classe per fare ciò.
Apriamo il REPL di Node.js per verificarlo di persona. Nel tuo terminale, inserisci il comando node
:
Vedrai il prompt iniziare con >
.
La funzione alloc()
prende la dimensione del buffer come suo primo e unico argomento obbligatorio. La dimensione è un numero intero che rappresenta quanti byte di memoria utilizzerà l’oggetto buffer. Ad esempio, se volessimo creare un buffer di 1KB (kilobyte), equivalente a 1024 byte, inseriremmo questo nella console:
Per cre creare un nuovo buffer, abbiamo utilizzato la classe globalmente disponibile Buffer
, che ha il metodo alloc()
. Fornendo 1024
come argomento per alloc()
, abbiamo creato un buffer di dimensioni 1KB.
Per impostazione predefinita, quando si inizializza un buffer con alloc()
, il buffer viene riempito con zeri binari come segnaposto per dati successivi. Tuttavia, possiamo cambiare il valore predefinito se lo desideriamo. Se volessimo creare un nuovo buffer con 1
invece di 0
, impostiamo il secondo parametro della funzione alloc()
, fill
.
Nel tuo terminale, crea un nuovo buffer al prompt REPL che è riempito con 1
:
Abbiamo appena creato un nuovo oggetto buffer che fa riferimento a uno spazio in memoria che memorizza 1KB di 1
. Anche se abbiamo inserito un intero, tutti i dati memorizzati in un buffer sono dati binari.
I dati binari possono assumere molti formati diversi. Ad esempio, consideriamo una sequenza binaria che rappresenta un byte di dati: 01110110
. Se questa sequenza binaria rappresentasse una stringa in inglese usando lo standard di codifica ASCII, sarebbe la lettera v
. Tuttavia, se il nostro computer stesse elaborando un’immagine, quella sequenza binaria potrebbe contenere informazioni sul colore di un pixel.
Il computer sa come elaborarli in modo diverso perché i byte sono codificati in modo diverso. L’encoding dei byte è il formato del byte. Un buffer in Node.js utilizza lo schema di codifica UTF-8 per impostazione predefinita se è inizializzato con dati di stringa. Un byte in UTF-8 rappresenta un numero, una lettera (in inglese e in altre lingue), o un simbolo. UTF-8 è un superset di ASCII, il Codice Standard Americano per lo Scambio di Informazioni. ASCII può codificare byte con lettere inglesi maiuscole e minuscole, i numeri da 0 a 9, e alcuni altri simboli come il punto esclamativo (!) o il segno dell’et commerciale (&).
Se dovessimo scrivere un programma che può funzionare solo con caratteri ASCII, potremmo cambiare la codifica utilizzata dal nostro buffer con il terzo argomento della funzione alloc()
– encoding
.
Creiamo un nuovo buffer lungo cinque byte e memorizziamo solo caratteri ASCII:
Il buffer è inizializzato con cinque byte del carattere a
, utilizzando la rappresentazione ASCII.
Nota: Per impostazione predefinita, Node.js supporta le seguenti codifiche dei caratteri:
- ASCII, rappresentata come
ascii
- UTF-8, rappresentata come
utf-8
outf8
- UTF-16, rappresentata come
utf-16le
outf16le
- UCS-2, rappresentato come
ucs-2
oucs2
- Base64, rappresentato come
base64
- Esadecimale, rappresentato come
hex
- ISO/IEC 8859-1, rappresentato come
latin1
obinary
Tutti questi valori possono essere utilizzati nelle funzioni della classe Buffer che accettano un parametro di encoding
. Pertanto, questi valori sono tutti validi per il metodo alloc()
.
Fino ad ora abbiamo creato nuovi buffer con la funzione alloc()
. Ma a volte potremmo voler creare un buffer da dati che già esistono, come una stringa o un array.
Per creare un buffer da dati preesistenti, utilizziamo il metodo from()
. Possiamo utilizzare tale funzione per creare buffer da:
- Un array di interi: i valori interi possono essere compresi tra
0
e255
. - Un
ArrayBuffer
: Questo è un oggetto JavaScript che memorizza una lunghezza fissa di byte. - A string.
- Un altro buffer.
- Altri oggetti JavaScript che hanno una proprietà
Symbol.toPrimitive
. Quella proprietà dice a JavaScript come convertire l’oggetto in un tipo di dato primitivo:booleano
,null
,undefined
,numero
,stringa
, osimbolo
. Puoi leggere di più sui simboli nella documentazione JavaScript di Mozilla.
Vediamo come possiamo creare un buffer da una stringa. Nel prompt di Node.js, inserisci questo:
Ora abbiamo un oggetto buffer creato dalla stringa Il mio nome è Paul
. Creiamo ora un nuovo buffer da un altro buffer che abbiamo creato in precedenza:
Abbiamo ora creato un nuovo buffer asciiCopy
che contiene gli stessi dati di asciiBuf
.
Ora che abbiamo sperimentato la creazione di buffer, possiamo approfondire gli esempi di lettura dei loro dati.
Passaggio 2 — Lettura da un Buffer
Ci sono molti modi per accedere ai dati in un Buffer. Possiamo accedere a un singolo byte in un buffer o possiamo estrarre l’intero contenuto.
Per accedere a un byte di un buffer, passiamo l’indice o la posizione del byte desiderato. I buffer memorizzano i dati in modo sequenziale come gli array. Indicizzano anche i loro dati come gli array, partendo da 0
. Possiamo usare la notazione array sull’oggetto buffer per ottenere un singolo byte.
Vediamo come appare questo creando un buffer da una stringa nel REPL:
Ora leggiamo il primo byte del buffer:
Premendo INVIO
, il REPL visualizzerà:
Output72
L’intero 72
corrisponde alla rappresentazione UTF-8 della lettera H
.
Nota: I valori dei byte possono essere numeri tra 0
e 255
. Un byte è una sequenza di 8 bit. Un bit è binario e quindi può avere solo uno dei due valori: 0
o 1
. Se abbiamo una sequenza di 8 bit e due possibili valori per bit, allora abbiamo un massimo di 2⁸ possibili valori per un byte. Ciò si traduce in un massimo di 256 valori. Dato che iniziamo a contare da zero, significa che il nostro numero più alto è 255.
Facciamo lo stesso per il secondo byte. Inserisci quanto segue nel REPL:
Il REPL restituisce 105
, che rappresenta la lettera minuscola i
.
Infine, otteniamo il terzo carattere:
Vedrai 33
visualizzato nel REPL, che corrisponde a !
.
Proviamo a recuperare un byte da un indice non valido:
Il REPL restituirà:
Outputundefined
Questo è proprio come se avessimo provato ad accedere a un elemento in un array con un indice non corretto.
Ora che abbiamo visto come leggere singoli byte di un buffer, vediamo le nostre opzioni per recuperare tutti i dati memorizzati in un buffer in una sola volta. L’oggetto buffer dispone dei metodi toString()
e toJSON()
, che restituiscono l’intero contenuto di un buffer in due formati diversi.
Come suggerisce il suo nome, il metodo toString()
converte i byte del buffer in una stringa e la restituisce all’utente. Se utilizziamo questo metodo su hiBuf
, otterremo la stringa Hi!
. Proviamolo!
Nel prompt, inserisci:
La REPL restituirà:
Output'Hi!'
Quel buffer è stato creato da una stringa. Vediamo cosa succede se utilizziamo il metodo toString()
su un buffer che non è stato creato da dati di tipo stringa.
Creiamo un nuovo buffer vuoto di dimensione 10
byte:
Ora, utilizziamo il metodo toString()
:
Vedremo il seguente risultato:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
La stringa \u0000
è il carattere Unicode per NULL
. Corrisponde al numero 0
. Quando i dati del buffer non sono codificati come una stringa, il metodo toString()
restituisce la codifica UTF-8 dei byte.
Il toString()
ha un parametro opzionale, encoding
. Possiamo utilizzare questo parametro per cambiare la codifica dei dati del buffer restituiti.
Per esempio, se volessimo la codifica esadecimale per hiBuf
dovremmo inserire quanto segue nel prompt:
Quell’istruzione restituirà:
Output'486921'
486921
è la rappresentazione esadecimale dei byte che rappresentano la stringa Hi!
. In Node.js, quando gli utenti vogliono convertire la codifica dei dati da una forma all’altra, di solito inseriscono la stringa in un buffer e chiamano toString()
con la codifica desiderata.
Il metodo toJSON()
si comporta in modo diverso. Indipendentemente dal fatto che il buffer sia stato creato da una stringa o meno, restituisce sempre i dati come rappresentazione intera del byte.
Riusiamo i buffer hiBuf
e tenZeroes
per praticare l’uso di toJSON()
. Al prompt, inserisci:
Il REPL restituirà:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
L’oggetto JSON ha una proprietà type
che sarà sempre Buffer
. Questo perché i programmi possono distinguere questi oggetti JSON da altri oggetti JSON.
La proprietà data
contiene un array della rappresentazione intera dei byte. Avrai notato che 72
, 105
e 33
corrispondono ai valori che abbiamo ricevuto quando abbiamo estratto individualmente i byte.
Proviamo il metodo toJSON()
con tenZeroes
:
Nel REPL vedrai il seguente:
Output{ type: 'Buffer', data: [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
] }
Il type
è lo stesso come notato in precedenza. Tuttavia, i dati sono ora un array con dieci zeri.
Ora che abbiamo coperto i principali modi per leggere da un buffer, vediamo come modificare il contenuto di un buffer.
Passaggio 3 — Modificare un Buffer
Ci sono molti modi per modificare un oggetto buffer esistente. Similmente alla lettura, possiamo modificare i byte del buffer individualmente usando la sintassi dell’array. Possiamo anche scrivere nuovi contenuti in un buffer, sostituendo i dati esistenti.
Iniziamo osservando come possiamo cambiare singoli byte di un buffer. Ricordiamo la nostra variabile di buffer hiBuf
, che contiene la stringa Hi!
. Cambiamo ogni byte in modo che contenga invece Hey
.
Nella REPL, proviamo prima a impostare il secondo elemento di hiBuf
su e
:
Ora vediamo questo buffer come una stringa per confermare che stia memorizzando i dati corretti. Proseguiamo chiamando il metodo toString()
:
Verrà valutato come:
Output'H\u0000!'
Ottieniamo quell’output strano perché il buffer può accettare solo un valore intero. Non possiamo assegnarlo alla lettera e
; piuttosto, dobbiamo assegnargli il numero il cui equivalente binario rappresenta e
:
Ora quando chiamiamo il metodo toString()
:
Otteniamo questo output nella REPL:
Output'He!'
Per cambiare l’ultimo carattere nel buffer, dobbiamo impostare il terzo elemento all’intero che corrisponde al byte per y
:
Confermiamo utilizzando nuovamente il metodo toString()
:
La tua REPL visualizzerà:
Output'Hey'
Se proviamo a scrivere un byte che è fuori dal range del buffer, verrà ignorato e il contenuto del buffer non cambierà. Ad esempio, proviamo a impostare il non esistente quarto elemento del buffer su o
:
Possiamo confermare che il buffer non è cambiato con il metodo toString()
:
L’output è ancora:
Output'Hey'
Se volessimo cambiare il contenuto dell’intero buffer, possiamo utilizzare il metodo write()
. Il metodo write()
accetta una stringa che sostituirà il contenuto di un buffer.
Utilizziamo il metodo write()
per cambiare il contenuto di hiBuf
tornando a Hi!
. Nel tuo shell Node.js, digita il seguente comando al prompt:
Il metodo write()
ha restituito 3
nel REPL. Questo perché ha scritto tre byte di dati. Ogni lettera ha una dimensione di un byte, poiché questo buffer utilizza la codifica UTF-8, che utilizza un byte per ogni carattere. Se il buffer utilizzasse la codifica UTF-16, che ha un minimo di due byte per carattere, allora la funzione write()
avrebbe restituito 6
.
Verifica ora il contenuto del buffer utilizzando toString()
:
Il REPL produrrà:
Output'Hi!'
Questo è più veloce che dover cambiare ogni elemento byte per byte.
Se si cerca di scrivere più byte di quanto consentito dalla dimensione di un buffer, l’oggetto buffer accetterà solo i byte che ci stanno. Per illustrare, creiamo un buffer che memorizzi tre byte:
Ora proviamo a scrivere Cats
su di esso:
Quando viene valutata la chiamata a write()
, il REPL restituisce 3
, indicando che solo tre byte sono stati scritti nel buffer. Ora conferma che il buffer contenga i primi tre byte:
Il REPL restituisce:
Output'Cat'
La funzione write()
aggiunge i byte in ordine sequenziale, quindi sono stati inseriti nel buffer solo i primi tre byte.
In contrasto, creiamo un Buffer
che memorizza quattro byte:
Scriviamo gli stessi contenuti al suo interno:
Quindi aggiungiamo del nuovo contenuto che occupa meno spazio rispetto al contenuto originale:
Dato che i buffer scrivono in sequenza, partendo da 0
, se stampiamo i contenuti del buffer:
Saremo accolti con:
Output'Hits'
I primi due caratteri vengono sovrascritti, ma il resto del buffer rimane intatto.
A volte i dati che vogliamo nel nostro buffer preesistente non sono in una stringa ma risiedono in un altro oggetto buffer. In questi casi, possiamo utilizzare la funzione copy()
per modificare ciò che il nostro buffer sta memorizzando.
Creiamo due nuovi buffer:
I buffer wordsBuf
e catchphraseBuf
contengono entrambi dati di stringa. Vogliamo modificare catchphraseBuf
in modo che memorizzi Nananana Turtle!
invece di Not sure Turtle!
. Utilizzeremo copy()
per ottenere Nananana
da wordsBuf
a catchphraseBuf
.
Per copiare i dati da un buffer all’altro, utilizzeremo il metodo copy()
sul buffer che è la fonte delle informazioni. Quindi, poiché wordsBuf
ha i dati di stringa che vogliamo copiare, dobbiamo copiare così:
Il parametro target
in questo caso è il buffer catchphraseBuf
.
Quando inseriamo ciò nel REPL, restituisce 15
, indicando che sono stati scritti 15 byte. La stringa Nananana
utilizza solo 8 byte di dati, quindi sappiamo immediatamente che la nostra copia non è andata come previsto. Utilizzare il metodo toString()
per vedere il contenuto di catchphraseBuf
:
Il REPL restituisce:
Output'Banana Nananana!'
Per impostazione predefinita, copy()
ha preso l’intero contenuto di wordsBuf
e lo ha collocato in catchphraseBuf
. Dobbiamo essere più selettivi per il nostro obiettivo e copiare solo Nananana
. Riscriviamo quindi il contenuto originale di catchphraseBuf
prima di continuare:
La funzione copy()
ha alcuni parametri aggiuntivi che ci consentono di personalizzare quali dati vengono copiati nell’altro buffer. Ecco un elenco di tutti i parametri di questa funzione:
target
– Questo è l’unico parametro richiesto dicopy()
. Come abbiamo visto dal nostro utilizzo precedente, è il buffer in cui vogliamo copiare.targetStart
– Questo è l’indice dei byte nel buffer di destinazione da cui dovremmo iniziare a copiare. Per impostazione predefinita è0
, il che significa che copia i dati a partire dall’inizio del buffer.sourceStart
– Questo è l’indice dei byte nel buffer di origine da cui dovremmo copiare.sourceEnd
– Questo è l’indice dei byte nel buffer di origine in cui dovremmo smettere di copiare. Per impostazione predefinita, è la lunghezza del buffer.
Quindi, per copiare Nananana
da wordsBuf
in catchphraseBuf
, il nostro target
dovrebbe essere catchphraseBuf
come prima. Il targetStart
sarebbe 0
poiché vogliamo che Nananana
appaia all’inizio di catchphraseBuf
. Il sourceStart
dovrebbe essere 7
poiché è l’indice in cui Nananana
inizia in wordsBuf
. Il sourceEnd
dovrebbe continuare ad essere la lunghezza dei buffer.
Al prompt REPL, copia i contenuti di wordsBuf
in questo modo:
Il REPL conferma che sono stati scritti 8
byte. Nota come wordsBuf.length
sia usato come valore per il parametro sourceEnd
. Come per gli array, la proprietà length
ci dà la dimensione del buffer.
Ora vediamo i contenuti di catchphraseBuf
:
Il REPL restituisce:
Output'Nananana Turtle!'
Successo! Siamo riusciti a modificare i dati di catchphraseBuf
copiando i contenuti di wordsBuf
.
Puoi uscire dal Node.js REPL se lo desideri. Nota che tutte le variabili create non saranno più disponibili quando lo farai:
Conclusioni
In questo tutorial, hai appreso che i buffer sono allocazioni di lunghezza fissa in memoria che memorizzano dati binari. Hai prima creato i buffer definendo la loro dimensione in memoria e inizializzandoli con dati preesistenti. Hai quindi letto i dati da un buffer esaminando i singoli byte e utilizzando i metodi toString()
e toJSON()
. Infine, hai modificato i dati memorizzati da un buffer modificando i singoli byte e utilizzando i metodi write()
e copy()
.
I buffer offrono una grande comprensione su come i dati binari vengono manipolati da Node.js. Ora che puoi interagire con i buffer, puoi osservare i diversi modi in cui la codifica dei caratteri influisce sulla memorizzazione dei dati. Ad esempio, puoi creare buffer da dati di stringa che non sono codificati in UTF-8 o ASCII e osservarne la differenza di dimensione. Puoi anche prendere un buffer con UTF-8 e utilizzare toString()
per convertirlo in altri schemi di codifica.
Per apprendere i buffer in Node.js, puoi leggere la documentazione di Node.js sull’oggetto Buffer
. Se desideri continuare a imparare Node.js, puoi tornare alla serie How To Code in Node.js, o sfogliare progetti di programmazione e configurazioni sulla nostra pagina argomento Node.
Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js