La maggior parte di voi sarà d'accordo sul fatto che
Puoi leggere ulteriori informazioni al riguardo qui: Lavorare con hashCode e il metodo equals in Java
HashMap
, oggi, è l'argomento preferito di discussione durante le interviste. A volte ho avuto discussioni simili con i miei colleghi e questo mi ha davvero aiutato. Ora avrò una discussione del genere con te. Presumo che se sei interessato agli aspetti interni e al funzionamento di HashMap, allora hai già familiarità con le basi di HashMap , quindi salterò quella parte. Ma se sei nuovo a questo, ti suggerisco di andare al sito Java Docs . Prima di proseguire, ti consiglio vivamente di consultare il mio articolo precedente: Lavorare con hashCode e il metodo equals in Java. Contenuto di questo articolo:
- L'unica risposta possibile.
- Cos'è l'hashing.
- Un po' della lezione
Entry
. - Cosa fa il
put()
. - Come funziona il metodo
get()
. - Appunti
L'unica risposta possibile
Se qualcuno mi chiede di spiegare " Come funziona HashMap?" ", risponderò semplicemente: " Secondo i principi dell'Hashing ". Non potrebbe essere più semplice. Per capirlo e ottenere una risposta esaustiva, devi essere sicuro di conoscere le basi dell'Hashing. Giusto?Cos'è l'hashing
L'hashing nella sua forma più semplice è un modo per convertire qualsiasi variabile/oggetto in un codice univoco dopo aver applicato qualsiasi formula/algoritmo alle relative proprietà. Una vera funzione hash deve seguire la seguente regola: una funzione hash deve restituire lo stesso codice hash ogni volta che viene applicata agli oggetti uguali o uguali. In altre parole, due oggetti identici devono restituire a turno gli stessi codici hash.Nota: tutti gli oggetti in Java ereditano l'implementazione standard hashCode() della funzione descritta nella classe Object . Questa funzione restituisce un codice hash ottenuto convertendo l'indirizzo interno di un oggetto in un numero, che porta alla creazione di un codice univoco per ogni singolo oggetto. |
Un po' della classe Entry
Per definizione, una mappa è “un oggetto che memorizza valori e chiavi in coppia”. Abbastanza semplice, vero? Quindi, deve esserci una sorta di meccanismo in HashMap che memorizza coppie di valori e chiavi? Risposta: sì.HashMap
ha una classe interna Entry
che assomiglia a questa:
static class Entry implements Map.Entry
{
final K key;
V value;
Entry next;
final int hash;
...//остальной code тут…
}
Naturalmente, la classe Entry
ha una chiave e un valore memorizzati come attributi. La chiave è contrassegnata come final
e vediamo anche due campi aggiuntivi: next
e hash
. Cercheremo di comprendere lo scopo di questi campi man mano che l'articolo avanza.
Cosa fa il metodo Java put()?
Prima di immergerci nell'implementazione del metodoput()
, è molto importante capire che le istanze di una classe Entry
sono archiviate in un array. La classe HashMap definisce questa variabile come:
/**
* Размер таблицы, изменяется при необходимости. Длина всегда должна быть
* кратна двум!
*/
transient Entry[] table;
Ora dai un'occhiata al codice di implementazione del metodo put()
:
/**
* Связывает определенное meaning с определенным ключом в этой карте(map).
* Если карта перед этим содержала meaning для данного ключа, это meaning
* заменится на новое.
*
* @param key
* ключ с которым указанное meaning должно быть связано.
* @param value
* meaning которое должно быть связано с ключом.
* @return вернет предыдущее meaning связанное с key, or null
* если не было значений связанных с key. (Вернет null
* так же, если перед этим key был связан со meaningм null)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<k , V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
Scopriamolo passo dopo passo:
- Prima di tutto controlliamo se la chiave esiste. Se la chiave non esiste (
null
), il valore viene inserito nella tabella alla posizione zero perché il codice hash per il valore ènull
,это – всегда 0
. - Nel passaggio successivo si calcola il valore hash utilizzando il codice hash della chiave ottenuto richiamando il metodo
hashCode()
. Questo valore hash viene utilizzato per calcolare la posizione nell'array in cui verrà posizionato l'oggettoEntry
. I progettisti JDK presupponevano che una funzione scritta malehashCode()
potesse restituire un valore hash troppo alto o troppo basso. Per risolvere questo problema, hanno introdotto un'altrahash()
funzione e vi hanno passato il valore hash di un oggetto per fare in modo che il valore hash corrispondesse alla dimensione dell'array. - Ora viene chiamata la funzione
indexFor(hash, table.length)
per calcolare la posizione esatta in cui verrà posizionato l'oggettoEntry
. - È qui che inizia la parte principale. Ora, in base a ciò che sappiamo che due oggetti non uguali possono avere codici hash uguali, poniamo la domanda: due oggetti diversi verranno posizionati nella stessa posizione nell'array [bucket]? La risposta è
LinkedList
. Se ricordi, la classeEntry
ha un attributo "next
". Questo attributo punta sempre all'oggetto successivo nella catena. Questo è esattamente il comportamentoLinkedList
.
Entry
vengono archiviati nel formato LinkedList
. Quando un oggetto Entry
deve essere posizionato in una posizione specifica, HashMap controlla se esiste già una voce in quella posizione. Se non è presente alcuna voce, l'oggetto viene posizionato in questa posizione. Se tuttavia in questa posizione è già presente un oggetto, viene controllato l'attributo successivo. Se ritorna null
e l'oggetto corrente Entry
diventa il collegamento successivo nel file LinkedList
. Se la variabile successiva non è null
, la procedura viene ripetuta per quella successiva finché non viene trovata null
. E se mettessimo un altro oggetto con un valore diverso ma con la stessa chiave di prima? Logicamente questo dovrebbe comportare la sostituzione del vecchio valore. Come avviene questo? In generale, dopo aver determinato la posizione di un oggetto Entry
, mentre si raggiunge LinkedList
la posizione calcolata, HashMap
richiama il metodo di confronto dei tasti per ciascun oggetto Entry
. Tutti questi Entry
oggetti LinkedList
possono avere codici hash simili, ma il metodo equals()
verificherà la reale somiglianza. Questo sostituirà solo il valore all'interno di Entry
. Pertanto, HashMap garantisce l'unicità di tutte le chiavi.
Come funziona il metodo Java get()?
Ora abbiamo un'idea di come vengono archiviate le coppie chiave-valore nei fileHashMap
. La prossima grande domanda è: cosa succede quando un oggetto viene passato da una HashMap a un metodo get()
? Come viene determinato il valore di un oggetto? Dovremmo già conoscere la risposta, perché il modo in cui viene determinata l'unicità di una chiave nel metodo put()
ha la stessa logica che applica il metodo get()
. Una volta HashMap
determinata la chiave dell'oggetto passato come argomento, restituisce semplicemente il valore del corrispondente Entry
. Se non viene trovata alcuna corrispondenza, il metodo get()
restituirà null
. Diamo un'occhiata al codice:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<k,V>e=table[indexFor(hash,table.length)];e!=null;e=e.next){
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
Il codice riportato sopra è simile al metodo utilizzato put()
fino a questo punto if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
, dopodiché restituisce semplicemente il valore dell'oggetto.
Appunti
- La struttura dati da memorizzare in un oggetto
Entry
è un array con un nometable
e un tipoEntry
. - Ogni singola posizione nell'array è chiamata bucket perché può contenere il primo elemento
LinkedList
degli oggettiEntry
. hashCode()
La chiave è necessaria per calcolare la posizione dell'oggettoEntry
.equals()
La chiave viene utilizzata per verificare l'unicità della chiave nella mappa (map
).hashCode()
eequals()
i valori non vengono utilizzati nei metodiget()
eset()
nei fileHashMap
.- Il codice hash per le chiavi con un valore
null
è sempre 0. E tale oggettoEntry
verrà sempre archiviato nella posizione zero dell'array.
GO TO FULL VERSION