Comprendere il tipo Map di Java

26 giugno 2017

Una Map (Mappa) rappresenta il tipo astratto che definisce una struttura dati in grado di memorizzare elementi nella forma di coppie chiave-valore. Ogni elemento all’interno di una mappa è identificato da una determinata chiave, la chiave consente non solo di recuperare ed inserire un elemento in una mappa, ma anche di definire un ordinamento per le implementazioni che prevedono questa funzionalità.

HashMap, HashTable e TreeMap

All’interno del linguaggio Java una Map è definita attraverso l’intefaccia java.util.Map e il linguaggio stesso offre diverse implementazioni. HashMap, HashTable e TreeMap sono quelle utilizzate più frequentemente e le caratteristiche che le differenziano sono essenzialmente le seguenti:

  • HasMap non è dotata di metodi sincronizzati ma consente elementi con valore null;
  • HashTable è dotata di metodi sincronizzati ma non consente elementi con valore null;
  • TreeMap è una mappa che definisce un ordinamento degli elementi basato sul valore delle chiavi.

Le implementazioni di Map devono garantire efficienti operazioni di inserimento e recupero dati, ragion per cui i metodi dell’interfaccia Map,get() e put() dedicati a queste funzionalità, devono essere realizzati garantendo le migliori performance possibili.

Le implementazioni di HashMap ed HashTable raggiungono questo obiettivo offrendo una complessità di tempo costante O(1) per entrambi i metodi. TreeMap, dovendo gestire un ordinamento, non è in grado di mantenersi allo stesso livello prestazionale, una TreeMap raggiunge comunque un’ottimale complessità di tempo del tipo O(N*LogN), dove N è il numero di elementi contenuti.

Come vedremo, scrivere codice che utilizzi una HashMap, HashTable o TreeMap non presenta particolari difficoltà, l’aspetto più importante è invece rappresentato dalla conoscenza delle basi dell’implementazione interna di una Map. Per imparare ad utilizzare al meglio il tipo Map, abbiamo la necessità di comprendere i concetti di Hash Function, Hash Value e Bucket.

Come sappiamo la classe Object definisce due metodi: equals() e hashCode(). hashCode() rappresenta la Hash Function e il valore di ritorno dalla sua implementazione consente di recuperare un elemento da una mappa. E’ importante non dimenticare il contratto che lega equals() e hashCode(): se eseguiamo l’override di equals() lo dobbiamo fare anche di hashCode(), tenendo in considerazione che se equals() restituisce true su due oggetti, allora l’invocazione di hashCode() sui due oggetti deve restituire lo stesso valore.

Come è quindi facile intuire l’Hash Value rappresenta il valore ritornato di hashCode():

public int hashCode(){
  //implementazione che calcola l'hash value
}

Bucket

Il concetto di Bucket è invece meno immediato da comprendere, ma rappresenta il mattone base per l’implementazione della struttura interna di una mappa. Un Bucket è costituito da una Linked List, semplificata per l’uso con le mappe e differente da quella contenuta nel package java.util, che memorizza coppie chiave-valore attraverso oggetti di una classe denominata Entry. Entry è dichiarata internamente all’interfaccia Map.

Per comprendere il meccanismo di Hashing nell’accesso al Bucket, consideriamo l’implementazione generale del metodo get() per la classe HashMap:

  public  V get(Object key) {
    if (key ==null) 
       //Codice di gestione da eseguire
    
    int hashValue = hash(key.hashCode());
    
    //Utilizzo del valore hash per recuperare  l'entry all'interno del bucket
   }
 

Il valore restituito da hashCode() non viene utilizzato direttamente, ma passato ad una funzione di hash() interna alla classe per recuperare l’effettivo valore per l’accesso al Bucket.

Il motivo dell’uso di un’ulteriore funzione di Hash è legato alla protezione da cattive implementazioni del metodo hashCode(). La struttura del Bucket non implementa una corrispondenza 1 a 1 tra coppie chiave-valore (Map.Entry) e valori hash. Può accadere che due oggetti distinti abbiamo lo stesso valore restituito da hashCode(), cosa perfettamente lecita in quanto non viola il contratto equals()/hashCode().

Per oggetti di questo tipo avremo una posizione del Bucket non contenente un solo oggetto Entry, ma una lista di oggetti Entry caratterizzati dallo stesso valore hash. In questo caso l’individuazione dell’oggetto da recuperare avviene tramite la scansione della lista di oggetti Entry nella locazione del Bucket individuata dal valore hash. Si utilizza quindi equals() per confrontare l’oggetto chiave key passato come parametro del metodo get(), e le chiavi degli oggetti Entry appartenenti alla lista.

Nel successivo capitolo concluderemo la trattazione del Bucket descrivendo i parametri capacity e load factor di una Mappa.

Tutte le lezioni

1 ... 60 61 62 ... 122

Se vuoi aggiornamenti su Comprendere il tipo Map di Java inserisci la tua e-mail nel box qui sotto:
Tags:
 
X
Se vuoi aggiornamenti su Comprendere il tipo Map di Java

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento di cui al punto 3 dell'informativa sulla privacy