"Sono stato bocciato a parecchi esami. Un mio amico invece, li ha passati tutti a pieni voti. Ora è ingegnere e lavora in Microsoft. Io sono il proprietario."  - Bill Gates  •  "Se riesco a fare qualcosa in 10 minuti, è perché ho passato anni a studiarlo."  - Bill Gates  •  "Il vostro lavoro riempirà gran parte della vostra vita, e l'unico modo per essere veramente soddisfatti è fare ciò che ritenete sia un grande lavoro."  - Steve Jobs  •  "Siate affamati. Siate folli."  - Steve Jobs  •  

cinque 

ZERO

{JS} Typed Array: sistemi numerici e buffer

home / javascript /

Lucio Asciolla

Full Stack Developer

Indice

Cosa sono i Typed Array?

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.

Linguaggio macchina

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.

Sistema binario, ottale, decimale e esadecimale

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 numericocifre binarie disponibili per la rappresentazione
BASE20 e 1
BASE80, 1, 2, 3, 4, 5, 6, e 7
BASE100, 1, 2, 3, 4, 5, 6, 7, 8 e 9
BASE160, 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.

Notazione posizionale

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 rappresentareBinarioOttaleDecimaleEsadecimale
00000
11111
210222
311333
4100444
5101555
6110666
7111777
810001088
910011199
1010101210A
1110111311B
1211001412C
1311011513D
1411101614E
1511111715F

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 binarieSistema numericorappresentazioni possibili
8binario8bit alias 1byte
8ottale128bit alias 16byte
8decimale256bit alias 32byte
8esagesimale512bit 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. 

Dati binari

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.

Typed Array

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.

Cosa sono i buffer?

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.

new ArrayBuffer

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 :

  • Ha una dimensione prefissata, non possiamo né aumentarla né diminuirla.
  • Per accedere a uno specifico byte, è richiesto un ulteriore oggetto visualizzatore (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

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:

OggettoTipo di dato
new Uint8Array(buffer)per accedere ai dati 8 bit (1byte) per volta
new Uint16Array(buffer)per accedere ai dati 16 bit (2byte) per volta
new Uint32Array(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.

Visualizzatore

"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.

new TypedArray

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 Uint8ArrayUint32Array, 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]);
ArgomentoDescrizione
bufferindica l'ArrayBuffer di riferimento (spazio in memoria allocato)
byteOffsetindica il punto da cui iniziare; di default è 0 ed indica l'inizio
lengthindica 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.

Proprietà e metodi comuni

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])

.splice()

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.

.set(fromArr, [offset])

Copia tutti gli elementi fino alla posizione fromArr , iniziando dalla posizione offset (0 di default).

.subarray([begin, end])

Crea un nuovo visualizzatore dello stesso tipo, a partire da begin fino a end (esclusi)

new DataView

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.

MetodoDescrizione
.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);
Argomenti collegati