←
{JS} Typed Array: sistemi numerici e buffer
home / javascript /
Lucio Asciolla
Full Stack Developer
I Typed Array
sono particolari strutture dati che consentono la manipolazione di dati binari.
Prima di procedere eseguiamo una breve introduzione al linguaggio macchina ed ai vari sistemi numerici.
I computer non comprendono le parole e numeri.
Nel livello più basso nel computer tutto è rappresentato da un segnale elettrico binario che può "esistere" solo in due stati fisici ben definiti:
acceso, nel caso si registri un passaggio di corrente elettrica, o spento, se non si registra il passaggio di corrente elettrica.
Questi stati fisici sono rappresentati nel linguaggio del computer attraverso un sistema binario che consente al processore di eseguire le operazioni alla base dei moderni software e applicativi.
Questo articolo è visibile in quanto, i chip e i transistor che compongono l'unità di elaborazione dati (CPU o GPU) del tuo dispositivo sono attraversati da un flusso continuo di elettroni (generati dalla batteria o dal cavo di alimentazione) che determina il comportamento esteriore del dispositivo come ad esempio l'accensione o meno dei pixel dello schermo.
Il codice binario è alla base del funzionamento di ciascun sistema informatico che utilizziamo oggi, indipendentemente dalla sua tipologia e fattore di forma.
Quando si parla di sistema binario ci si riferisce a un sistema numerico in BASE 2; quindi ad un sistema numerico composto solo da due cifre, 0 e 1, che corrispondono agli stati fisici, acceso e spento, dei transistor che compongono i vari chip.
Quando parliamo di sistema numerico a BASE 8 o sistema ottale, parliamo di un sistema numerico che si compone di 8 cifre 0, 1, 2, 3, 4, 5, 6, 7.
Quando parliamo di sistema numerico a BASE 10 o sistema decimale, parliamo di un sistema numerico, simile al sistema binario, che si compone di dieci cifre: 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9.
Invece, il sistema esagesimale, di numerazione in BASE 16 (spesso abbreviato come esa o hex), utilizza i numeri da 0 a 9 a cui si aggiungono le prime sei lettere dell’alfabeto a, b, c, d, e, f.
Sistema numerico | cifre binarie disponibili per la rappresentazione |
BASE2 | 0 e 1 |
BASE8 | 0, 1, 2, 3, 4, 5, 6, e 7 |
BASE10 | 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9 |
BASE16 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E e F |
Quando parliamo di "cifre" in realtà parliamo di "cifre binarie"; una cifra-binaria è un "valore" utilizzabile assieme ad altre cifre-binarie per rappresentare un dato.
Finora quel che sappiamo è che abbiamo a disposizione molteplici sistemi-numerici con differenti numeri di cifre-binare a disposizione.
Tutti i principali sistemi numerici sono con “notazione posizionale“.
La notazione posizionale è un metodo di scrittura dei numeri, nel quale ogni posizione è collegata alla posizione vicina da un moltiplicatore, chiamato "base" del sistema di numerazione.
Senza entrare nello specifico, grazie alla "notazione posizionale" ed al posizionamento delle cifre-binarie è possibile rappresentare un dato con un numero sempre inferiore di cifre-binarie a seconda del numero di valori disponibile dal sistema-numerico adottato e dal proprio moltiplicatore.
Tutti i sistemi numerici si basano sul "binary digit", abbreviato in "bit".
Una cifra-binaria corrisponde sempre ad 1 bit.
I sistemi numerici, BASE2, BASE8, BASE10 e BASE16, richiedono un preciso numero di cifre-binarie e quindi di byte per rappresentate un dato:
Numero da rappresentare | Binario | Ottale | Decimale | Esadecimale |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 | 1 |
2 | 10 | 2 | 2 | 2 |
3 | 11 | 3 | 3 | 3 |
4 | 100 | 4 | 4 | 4 |
5 | 101 | 5 | 5 | 5 |
6 | 110 | 6 | 6 | 6 |
7 | 111 | 7 | 7 | 7 |
8 | 1000 | 10 | 8 | 8 |
9 | 1001 | 11 | 9 | 9 |
10 | 1010 | 12 | 10 | A |
11 | 1011 | 13 | 11 | B |
12 | 1100 | 14 | 12 | C |
13 | 1101 | 15 | 13 | D |
14 | 1110 | 16 | 14 | E |
15 | 1111 | 17 | 15 | F |
Come è possibile vedere per rappresentare i numeri da 0 a 15 il sistema-numerico più dispendioso è il sistema binario che utilizza 4 cifre-binarie che equivalgono a 4 bit; mentre, quello meno dispendioso è il sistema esagesimale che utilizza solo 1 bit.
Un byte (un boccone) rappresenta, nel sistema binario, la sequenza di 8 bit ed e’ divenuto storicamente l’elemento base e l’unita’ di misura base dell’informazione.
Questo è vero, solo nel sistema binario che è all'origine di tutto.
Il byte rimane sempre l'unità di misura base ma a seconda dei sistemi-numerici di riferimento possiamo trasportare molte più informazioni.
Prendiamo in esame una sequenza di 8 cifre-binarie e verifichiamo le diverse possibilità di rappresentazione in base ai sistemi numerici.
Cifre binarie | Sistema numerico | rappresentazioni possibili |
8 | binario | 8bit alias 1byte |
8 | ottale | 128bit alias 16byte |
8 | decimale | 256bit alias 32byte |
8 | esagesimale | 512bit alias 64byte |
8 cifre-binarie nel sistema binario rappresentano solo 8bit di informazioni;
nel sistema ottale lo stesso numero di cifre possono rappresentare fino a 128bit; decimale 256bit ed in fine l'esagesimale 512bit.
Ne consegue che con meno cifre-binare è possibile rappresentare, a seconda del sistema di numerazione, più bit di informazioni.
Tutto questo è possibile grazie alla notazione posizionale ed ai vari sistemi di numerazione.
Il sistema binario risulta quindi più complesso da capire, ma anche quello meno efficiente.
I dati binari o file binario, in Informatica, è un file che può contenere qualsiasi tipo di dati, codificato in codice binario a scopo di archiviazione o utilizzo.
I file binari sono concepiti come sequenze di bit che costituiscono il file solitamente raggruppate in gruppi di otto, 1byte.
In termine "Typed Array" rappresenta semplicemente un termine comune per rappresentare tutti i diversi tipi di array che consentono la manipolare di dati binari contenuti in un buffer.
Le specifiche prevedono due elementi per la gestione di array di dati binari: ArrayBuffer
e ArrayBufferView
.
Per manipolare dati binari dovremo lavorare con i buffer.
Un buffer (tradotto "tampone") è una porzione di memoria usata per contenere provvisoriamente dati destinati ad essere rielaborati, cancellati o trasferiti in un'altra unità di memoria secondaria e che ha lo scopo di compensare le differenti velocità di trattamento tra le due unità.
Quando vediamo un video o film su Netflix, Prime Video o Youtube, per consentirci la visualizzazione è necessario un buffer; un buffer si occupa del trasferimento modulare/porzionato del file-totale presente sui server dei servizi di intrattenimento alla memoria del nostra tv o dispositivo.
Il file totale che contiene il video o film sarà trattato come file-binario o dato-binario.
ArrayBuffer è un particolare oggetto/costruttore che rappresenta un blocco di dati e serve per contenere una generica sequenza di byte.
Non indica quindi alcun formato specifico nè un contenuto specifico, semplicemente alloca uno spazio di memoria contigua grande quanto i byte indicati e la popola con degli zeri.
Pensiamo ad esso come ad un costruttore di spazi di memoria, o ad container di memoria, per sequenze di byte.
var buffer = new ArrayBuffer(16); console.log(`Memoria disponibile per il buffer : ${buffer.byteLength} bit`);
L'istanza generata eredita l'unica proprietà disponibile ".byteLength" che recupera il valore assegnato in bit al nostro buffer.
L'ArrayBuffer, contrariamente al nome assegnato al costruttore, non ha nulla a che vedere con gli array :
buffer[index]
non funziona)Nota: attualmente stiamo allocando generici spazi di memoria da gestire; successivamente lo stesso sarà rappresentato dalle dimensioni-totali di un file.
ArrayBufferView non è un costruttore ma è il nome comune utilizzato per indicare l'insieme dei diversi tipi di oggetti costruttori utilizzabili per interpretare i dati di un buffer:
Oggetto | Tipo di dato |
---|---|
new (buffer) | per accedere ai dati 8 bit (1byte) per volta |
new (buffer) | per accedere ai dati 16 bit (2byte) per volta |
new (buffer) | per accedere ai dati 32 bit (4byte) per volta |
new Float64Array (buffer) | per accedere ai dati 64 bit (8byte) per volta |
ArrayBufferView sta ad indicare "la modalità di lettura" da utilizzare sulla memoria precedentemente allocata.
Note: prestiamo attenzione; la memoria allocata è indicata in byte mentre le varie "modalità di lettura" sono suddivise in bit.
var buffer = new ArrayBuffer(16); var bufferView = new Uint8Array(buffer);
Nella variabile "buffer" abbiamo creato un memoria totale per un buffer di 16byte;
Nella variabile "bufferView" abbiamo impostato, la modalità di lettura; ovvero, che la memoria totale di 16byte sia letta 8bit (1byte) per volta.
16 byte (memoria allocata ArrayBuffer) equivalgono a 128 bit / 8 (divisione Uint8Array) = 16 porzioni di lettura
L'istanza che creeremo mediante i vari costruttori conterrà un array di dati-binari suddivisi secondo il tipo di la modalità di lettura scelta.
"BufferView" che potremmo tradurre in "Vista del buffer" è quello che viene generalmente chiamato un "visualizzatore".
Di fatto rappresenta una suddivisione modulare della memoria totale allocata ad un buffer e possiede simil "proprietà" di un array di dati semplice.
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,]
Le istanze generate dai vari costruttori "ArrayBufferView" sono quindi "visualizzatori".
Un visualizzatore possiede proprietà per recuperare di varie informazioni, consente di associare valori ai vari chunk, cosi come lo si fa con un array semplice, e di provvedere all'iterazione dello stesso.
Proprietà | Descrizione |
bufferView.lenght; | mostra il numero di chunk presenti |
bufferView.byteLength; | mostra la dimensione totale del buffer |
//Allochiamo memoria totale al buffer di 16byte var buffer = new ArrayBuffer(16); //Suddividiamo il buffer in 8bit che equivalgono a 16 chunk var bufferView = new Uint8Array(buffer); //Mostriamo il numero di chunk totali console.log(`Il buffer contiene: ${bufferView.length} chunk`); //Mostriamo la memoria totale allocata in byte al buffer console.log(`Memoria totale buffer: ${bufferView.byteLength} byte`); //Calcoliamo la dimensione di un singolo chunk console.log(`Dimensione di un singolo chunk: ${bufferView.byteLength/bufferView.length} byte`); //Leggiamo il contenuto del primo chunk var primoChunk = bufferView[0]; console.log(`Contenuto del primo chunk: ${primoChunk}`); //Iteriamo su tutti i chunk del buffer for( i=0; i<bufferView.length; i++){ console.log(`Chunk n. ${i} contiene ${bufferView[i]}`); }
Tutti i chunk conterranno valori uguali a zero; come anticipato precedentemente, stiamo lavorando con un buffer generico privo di informazioni al suo interno.
Abbiamo detto che "TypedArray" rappresenta un termine-comune per indicare/raggruppare sotto un unica voce i vari tipi di buffer tipizzati.
Esso rappresenta "simbolicamente" anche un costruttore.
Il costruttore "new TypedArray" non esiste, simboleggia e funge da "ombrello" su tutte i tipi di array tipizzati.
Quindi "new TypedArray()" potrà assumere le sembianze di Uint8Array
, Uint32Array
, etc
Qual'è allora la differenza tra TypedArray e ArrayBufferView?
In realtà la differenza è estremamente sottile.
"TypedArray" indica tutti gli array tipizzati ed è coniato per raggruppare sotto un unica voce, le proprietà ed i metodi comuni tra tutti questi.
Il termine "ArrayBufferView" indica semplicemente l'insieme di tutti i visualizzatori.
new TypedArray(buffer, [byteOffset], [length]); new TypedArray(object); new TypedArray(typedArray); new TypedArray(length); new TypedArray();
I vari tipi di "TypedArray" accettano diverse combinazioni di argomenti.
L'uso più comune è il seguente con tre argomenti:
new TypedArray(buffer, [byteOffset], [length]);
Argomento | Descrizione |
buffer | indica l'ArrayBuffer di riferimento (spazio in memoria allocato) |
byteOffset | indica il punto da cui iniziare; di default è 0 ed indica l'inizio |
length | indica il punto dove terminare; di default sino alla fine |
Se omettiamo argomenti o includiamo altri tipi di argomenti un TypedArray potrà comportarsi in modi differenti.
Non affronteremo nello specifico questo argomento, trattandosi di usi molto particolari, ci basti sapere che un TypedArray ha anche la possibilità di auto-completare informazioni necessarie in presenza di particolari argomenti.
Avevamo già visto precedentemente le proprietà in comune a tutti i visualizzatori:
Proprietà | Descrizione |
TypedArray.lenght; | mostra il numero di chunk presenti |
TypedArray.byteLength; | mostra la dimensione totale del buffer |
Questi invece sono i metodi disponibili che sono simili a quelli dei semplici array con alcune limitazioni:
Metodi |
TypedArray.map() |
TypedArray.slice() |
TypedArray.splice() |
TypedArray.find() |
TypedArray.reduce() |
TypedArray.set(fromArr, [offset]) |
TypedArray.subarray([begin, end]) |
Il metodo ".splice()" presenta una limitazione; non possiamo “eliminare” un valore, poiché i TypedArray sono dei visualizzatori su un buffer, e questi sono di dimensioni fissa. Tutto ciò che possiamo fare è assegnargli zero.
Copia tutti gli elementi fino alla posizione fromArr
, iniziando dalla posizione offset
(0 di default).
Crea un nuovo visualizzatore dello stesso tipo, a partire da begin
fino a end
(esclusi)
DataView è uno speciale visualizzatore “non tipizzato”.
Consente di accedere ai dati in qualsiasi offset e in qualsiasi formato.
In informatica un offset è un numero intero che indica la distanza tra due elementi all'interno di un gruppo di elementi dello stesso tipo.
Il visualizzatore "DataView" agisce su un buffer pre-configurato.
var buffer = new ArrayBuffer(32); var bufferView = new Uint8Array(buffer); var dataView = new DataView(buffer);
Il visualizzatore non ha un tipo di visualizzazione standard;
pensiamo a "DataView" come ad un visualizzatore sopra ad un visualizzatore.
Debbiamo precisare che quando applichiamo un visualizzatore ad un buffer non suddividiamo il buffer vero e proprio ma gli diciamo semplicemente di leggerlo in una determinata maniera.
Nell'immagine qui sopra non abbia creato alcun visualizzatore, eppure, se ispezioniamo il buffer, vedremo che esistono già tutte le suddivisioni possibili all'interno del buffer stesso.
Questo ha metodi particolare, diversificati, per la lettura ed anche scrittura), dei byte contenuti in un visualizzatore pre-esistente.
Metodo | Descrizione |
.getUint8(i) .setUint8(i, value) | 1. leggi il buffer 8bit per volta e prendi la posizione con indice "n" 2. leggi il buffer 8bit per volta e la posizione con indice "n" ed imposta il seguente valore |
.getUint16(i) .setUint16(i, value) | 1. leggi il buffer 16bit per volta e prendi la posizione con indice "n" 2. leggi il buffer 16bit per volta e la posizione con indice "n" ed imposta il seguente valore |
.getUint32(i) .setUint32(i, value) | 1. leggi il buffer 32bit per volta e prendi la posizione con indice "n" 2. leggi il buffer 32bit per volta e la posizione con indice "n" ed imposta il seguente valore |
var buffer = new ArrayBuffer(32); var bufferView = new Uint8Array(buffer); var dataView = new DataView(buffer); dataView.getUnit8(0); dataView.setUnit8(0, 0101);