Come fa un Java HashMap a gestire oggetti diversi con lo stesso codice hash?


223

Secondo la mia comprensione penso:

  1. È perfettamente legale che due oggetti abbiano lo stesso hashcode.
  2. Se due oggetti sono uguali (usando il metodo equals ()) allora hanno lo stesso hashcode.
  3. Se due oggetti non sono uguali, non possono avere lo stesso hashcode

Ho ragione?

Ora, se ho ragione, ho la seguente domanda: L' HashMapinterno usa l'hashcode dell'oggetto. Quindi, se due oggetti possono avere lo stesso hashcode, come può la HashMaptraccia quale chiave usa?

Qualcuno può spiegare come l' HashMapinterno utilizza l'hashcode dell'oggetto?


29
Per la cronaca: # 1 e # 2 sono corretti, # 3 è sbagliato: due oggetti non uguali possono avere lo stesso codice hash.
Joachim Sauer,

6
Il n. 1 e il n. 3 sono persino contraddittori
Delfic,

Infatti, se non si segue # 2, l'implementazione equals () (o probabilmente hashCode ()) non è corretta.
Joachim Sauer

Risposte:


346

Una hashmap funziona in questo modo (è un po 'semplificata, ma illustra il meccanismo di base):

Ha un numero di "bucket" che utilizza per memorizzare coppie chiave-valore. Ogni bucket ha un numero univoco: questo è ciò che identifica il bucket. Quando si inserisce una coppia chiave-valore nella mappa, l'hashmap esaminerà il codice hash della chiave e memorizzerà la coppia nel bucket di cui l'identificatore è il codice hash della chiave. Ad esempio: il codice hash della chiave è 235 -> la coppia è memorizzata nel numero di bucket 235. (Notare che un bucket può memorizzare più di una coppia chiave-valore).

Quando cerchi un valore nella hashmap, assegnandogli una chiave, per prima cosa esaminerà il codice hash della chiave che hai fornito. L'hashmap esaminerà quindi il bucket corrispondente e quindi confronterà la chiave fornita con le chiavi di tutte le coppie nel bucket, confrontandole con equals().

Ora puoi vedere come questo è molto efficace per cercare coppie chiave-valore in una mappa: dal codice hash della chiave, l'hashmap sa immediatamente in quale bucket cercare, in modo che debba solo testare ciò che è in quel bucket.

Guardando il meccanismo sopra, puoi anche vedere quali requisiti sono necessari sui metodi hashCode()e equals()delle chiavi:

  • Se due chiavi sono uguali ( equals()restituisce truequando le si confronta), il loro hashCode()metodo deve restituire lo stesso numero. Se le chiavi violano questo, le chiavi uguali potrebbero essere memorizzate in diversi bucket e l'hashmap non sarebbe in grado di trovare coppie chiave-valore (perché cercherà nello stesso bucket).

  • Se due chiavi sono diverse, non importa se i loro codici hash sono uguali o meno. Saranno archiviati nello stesso bucket se i loro codici hash sono uguali e, in questo caso, la hashmap utilizzerà equals()per distinguerli.


4
hai scritto "e l'hashmap non sarebbe in grado di trovare coppie chiave-valore (perché guarderà nello stesso bucket)." Puoi spiegare che sta andando nello stesso bucket per dire che questi due oggetti uguali sono t1 e t2 e sono uguali e t1 e t2 hanno hashcodes h1 e h2 rispettivamente. Quindi t1.equals (t2) = true e h1! = H2 Quindi quando l'hashmap cercherà t1, cercherà nel bucket h1 e per t2 nel bucket t2?
Geek,

19
Se due chiavi sono uguali ma il loro hashCode()metodo restituisce codici hash diversi, i metodi equals()e hashCode()della classe di chiavi violano il contratto e otterrai strani risultati quando si usano quelle chiavi in ​​a HashMap.
Jesper,

Ogni bucket può avere più coppie di valori chiave, che utilizzano internamente un elenco collegato. Ma la mia confusione è: cos'è il secchio qui? Quale struttura di dati utilizza internamente? C'è qualche collegamento tra i secchi?
Ankit Sharma,

1
@AnkitSharma Se vuoi conoscere davvero tutti i dettagli, cerca il codice sorgente di HashMap, che puoi trovare nel file src.zipnella directory di installazione di JDK.
Jesper,

1
@ 1290 L'unica relazione tra le chiavi nello stesso bucket è che hanno lo stesso codice hash.
Jesper,

88

La tua terza affermazione non è corretta.

È perfettamente legale che due oggetti disuguali abbiano lo stesso codice hash. Viene utilizzato HashMapcome "filtro di primo passaggio" in modo che la mappa possa trovare rapidamente possibili voci con la chiave specificata. Le chiavi con lo stesso codice hash vengono quindi testate per l'uguaglianza con la chiave specificata.

Non vorresti un requisito secondo il quale due oggetti disuguali non potevano avere lo stesso codice hash, altrimenti ciò ti limiterebbe a 2 32 possibili oggetti. (Significherebbe anche che tipi diversi non potrebbero nemmeno usare i campi di un oggetto per generare codici hash, poiché altre classi potrebbero generare lo stesso hash.)


6
come sei arrivato a 2 ^ 32 possibili oggetti?
Geek,

5
Sono in ritardo, ma per quelli che ancora si chiedono: un hashcode in Java è un int e un int ha 2 ^ 32 possibili valori
Xerus,

69

Diagramma della struttura di HashMap

HashMapè una matrice di Entryoggetti.

Considera HashMapsolo una matrice di oggetti.

Dai un'occhiata a cosa si Objecttratta:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

Ogni Entryoggetto rappresenta una coppia chiave-valore. Il campo si nextriferisce a un altro Entryoggetto se un bucket ne ha più di uno Entry.

A volte può capitare che i codici hash per 2 oggetti diversi siano uguali. In questo caso, due oggetti verranno salvati in un bucket e verranno presentati come un elenco collegato. Il punto di ingresso è l'oggetto aggiunto più di recente. Questo oggetto si riferisce a un altro oggetto con il nextcampo e così via. L'ultima voce si riferisce a null.

Quando si crea un HashMapcon il costruttore predefinito

HashMap hashMap = new HashMap();

L'array viene creato con dimensioni 16 e saldo di carico predefinito 0,75.

Aggiunta di una nuova coppia chiave-valore

  1. Calcola l'hashcode per la chiave
  2. Calcola la posizione in hash % (arrayLength-1)cui posizionare l' elemento (numero di bucket)
  3. Se si tenta di aggiungere un valore con una chiave in cui è già stato salvato HashMap, il valore viene sovrascritto.
  4. Altrimenti l'elemento viene aggiunto al bucket.

Se il bucket ha già almeno un elemento, ne viene aggiunto uno nuovo e posizionato nella prima posizione del bucket. Il suo nextcampo si riferisce al vecchio elemento.

cancellazione

  1. Calcola l'hashcode per la chiave specificata
  2. Calcola il numero di bucket hash % (arrayLength-1)
  3. Ottieni un riferimento al primo oggetto Entry nel bucket e, per mezzo del metodo uguale, esegui l'iterazione su tutte le voci nel bucket specificato. Alla fine troveremo il corretto Entry. Se un elemento desiderato non viene trovato, ritornanull

3
Questo è sbagliato hash % (arrayLength-1), sarebbe hash % arrayLength. Ma in realtà lo è hash & (arrayLength-1) . Cioè, perché utilizza potenze di due ( 2^n) per la lunghezza dell'array, prendendo i nbit meno significativi.
Weston,

Penso che non sia un array di oggetti Entity piuttosto un array di LinkedList / Tree. E ogni albero internamente ha oggetti Entity.
Mudit bhaintwal,

@shevchyk perché memorizziamo chiave e hash? a cosa servono Non stiamo sprecando memoria qui?
roottraveller,

hashset utilizza internamente hashmap. le regole di aggiunta e cancellazione di hashmap sono valide per hashset?
scambio eccessivo del

2
@weston non solo, hashCode è un intche ovviamente può essere negativo, fare modulo su un numero negativo ti darà un numero negativo
Eugene,

35

Puoi trovare informazioni eccellenti su http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html

Riassumere:

HashMap funziona secondo il principio dell'hashing

put (chiave, valore): HashMap memorizza sia l'oggetto chiave che valore come Map.Entry. Hashmap applica l'hashcode (chiave) per ottenere il bucket. in caso di collisione, HashMap utilizza LinkedList per archiviare l'oggetto.

ottenere (chiave): HashMap utilizza l'hashcode di Key Object per scoprire la posizione del bucket e quindi chiamare il metodo keys.equals () per identificare il nodo corretto in LinkedList e restituire l'oggetto valore associato per quella chiave in Java HashMap.


3
Ho trovato migliore la risposta fornita da Jasper, ho sentito che il blog è più orientato alla gestione delle interviste, piuttosto che alla comprensione del concetto
Narendra N

@NarendraN Sono d'accordo con te.
Abhijit Gaikwad,

22

Ecco una descrizione approssimativa del HashMapmeccanismo di, per Java 8versione, (potrebbe essere leggermente diverso da Java 6) .


Strutture dati

  • Tabella
    hash Il valore hash viene calcolato tramite hash()chiave e decide quale bucket dell'hashtable utilizzare per una determinata chiave.
  • Elenco collegato (singolarmente)
    Quando il conteggio degli elementi in un bucket è piccolo, viene utilizzato un elenco collegato singolarmente.
  • Albero rosso-nero
    Quando il conteggio degli elementi in un secchio è grande, viene utilizzato un albero rosso-nero.

Classi (interne)

  • Map.Entry
    Rappresenta una singola entità nella mappa, l'entità chiave / valore.
  • HashMap.Node
    Versione dell'elenco collegato del nodo.

    Potrebbe rappresentare:

    • Un secchio di hash.
      Perché ha una proprietà hash.
    • Un nodo in una lista singolarmente collegata (quindi anche capo della lista collegata) .
  • HashMap.TreeNode
    Versione ad albero del nodo.

Campi (interni)

  • Node[] table
    La tabella bucket, (capo degli elenchi collegati).
    Se un bucket non contiene elementi, è nullo, quindi occupa solo spazio di un riferimento.
  • Set<Map.Entry> entrySet Insieme di entità.
  • int size
    Numero di entità.
  • float loadFactor
    Indica quanto è consentita la tabella hash prima di ridimensionare.
  • int threshold
    La dimensione successiva alla quale ridimensionare.
    Formula:threshold = capacity * loadFactor

Metodi (interni)

  • int hash(key)
    Calcola l'hash per chiave.
  • Come mappare l'hash sul bucket?
    Usa la seguente logica:

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

A proposito di capacità

Nella tabella hash, capacità indica il numero di bucket, da cui potrebbe essere ricavato table.length.
Inoltre potrebbe essere calcolato tramite thresholdeloadFactor , quindi non è necessario definire un campo di classe.

Potrebbe ottenere la capacità effettiva tramite: capacity()


operazioni

  • Trova entità per chiave.
    Per prima cosa trova il bucket in base al valore hash, quindi esegui il loop dell'elenco collegato o cerca l'albero ordinato.
  • Aggiungi entità con chiave.
    Per prima cosa trova il bucket in base al valore hash della chiave.
    Quindi prova a trovare il valore:
    • Se trovato, sostituire il valore.
    • In caso contrario, aggiungere un nuovo nodo all'inizio dell'elenco collegato o inserirlo nella struttura ad albero ordinata.
  • Ridimensiona
    Una volta thresholdraggiunto, raddoppierà la capacità table.lengthdell'hashtable ( ), quindi eseguirà un re-hash su tutti gli elementi per ricostruire la tabella.
    Questa potrebbe essere un'operazione costosa.

Prestazione

  • get & put
    La complessità del tempo è O(1), perché:
    • Al bucket si accede tramite l'indice di array, quindi O(1).
    • L'elenco collegato in ogni bucket ha una lunghezza ridotta, pertanto potrebbe essere visualizzato come O(1).
    • Anche la dimensione dell'albero è limitata, poiché estenderà la capacità e la ripetizione dell'hash quando aumenta il conteggio degli elementi, quindi potrebbe vederlo come O(1)no O(log N).

Puoi per favore fare un esempio Com'è la complessità temporale O (1)
Jitendra

@jsroyal Questo potrebbe spiegare più chiaramente la complessità: en.wikipedia.org/wiki/Hash_table . Ma in breve: trovare il bucket di destinazione è O (1), perché lo trovi per indice in un array; quindi all'interno di un bucket, la quantità di elementi è piccola e in media un numero costante nonostante il numero totale di elementi nell'intera tabella hash, quindi anche la ricerca dell'elemento target all'interno di un bucket è O (1); quindi, O (1) + O (1) = O (1).
Eric Wang,

14

L'hashcode determina quale bucket deve essere controllato dall'hashmap. Se nel bucket è presente più di un oggetto, viene eseguita una ricerca lineare per trovare quale elemento nel bucket sia uguale all'articolo desiderato (utilizzando il equals()metodo).

In altre parole, se si dispone di un codice hash perfetto, quindi l'accesso hashmap è costante, non sarà mai necessario scorrere attraverso un bucket (tecnicamente dovresti anche avere bucket MAX_INT, l'implementazione Java può condividere alcuni codici hash nello stesso bucket per ridurre i requisiti di spazio). Se hai il peggior hashcode (restituisce sempre lo stesso numero), l'accesso alla hashmap diventa lineare poiché devi cercare tutti gli elementi della mappa (sono tutti nello stesso bucket) per ottenere ciò che desideri.

Il più delle volte un hashcode ben scritto non è perfetto, ma è abbastanza unico per darti un accesso più o meno costante.


11

Ti sbagli sul punto tre. Due voci possono avere lo stesso codice hash ma non essere uguali. Dai un'occhiata all'implementazione di HashMap.get da OpenJdk . Puoi vedere che controlla che gli hash siano uguali e che le chiavi siano uguali. Se il punto tre fosse vero, non sarebbe necessario verificare che le chiavi siano uguali. Il codice hash viene confrontato prima della chiave perché il primo è un confronto più efficiente.

Se sei interessato a saperne di più su questo, dai un'occhiata all'articolo di Wikipedia sulla risoluzione delle collisioni di Open Addressing , che credo sia il meccanismo che utilizza l'implementazione di OpenJdk. Tale meccanismo è leggermente diverso dall'approccio "benna" menzionato da una delle altre risposte.


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

Quindi qui vediamo che se entrambi gli oggetti S1 e S2 hanno contenuti diversi, allora siamo abbastanza sicuri che il nostro metodo Hashcode ignorato genererà Hashcode diverso (116232,11601) per entrambi gli oggetti. ORA poiché ci sono diversi codici hash, quindi non si preoccuperà nemmeno di chiamare il metodo EQUALS. Perché un codice hash diverso GARANTISCE DIVERSI contenuti in un oggetto.

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

3

due oggetti sono uguali, implica che hanno lo stesso hashcode, ma non viceversa.

2 oggetti uguali ------> hanno lo stesso hashcode

2 oggetti hanno lo stesso hashcode ---- xxxxx -> NON sono uguali

Aggiornamento di Java 8 in HashMap-

fai questa operazione nel tuo codice -

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

quindi, supponiamo che il tuo hashcode sia tornato per entrambe le chiavi "old"ed "very-old"è lo stesso. Quindi cosa accadrà.

myHashMapè una HashMap e supponiamo che inizialmente non sia stata specificata la sua capacità. Quindi la capacità predefinita di Java è 16. Quindi, non appena hai inizializzato l'hashmap usando la nuova parola chiave, ha creato 16 bucket. ora quando hai eseguito la prima dichiarazione-

myHashmap.put("old","old-value");

quindi "old"viene calcolato l' hashcode per , e poiché l'hashcode potrebbe essere anche un numero intero molto grande, quindi java lo ha fatto internamente - (l'hash è hashcode qui e >>> è il turno giusto)

hash XOR hash >>> 16

quindi per dare un'immagine più grande, verrà restituito un indice, che sarebbe compreso tra 0 e 15. Ora la coppia di valori chiave "old"e "old-value"verrà convertita in chiave dell'oggetto Entry e variabile dell'istanza di valore. e quindi questo oggetto voce verrà archiviato nel bucket, oppure si può dire che in un determinato indice questo oggetto voce verrà archiviato.

FYI- Entry è una classe nell'interfaccia Map- Map.Entry, con queste firme / definizioni

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

ora quando esegui la prossima dichiarazione -

myHashmap.put("very-old","very-old-value");

e "very-old"fornisce lo stesso hashcode di "old", quindi questa nuova coppia chiave-valore viene nuovamente inviata allo stesso indice o allo stesso bucket. Ma poiché questo bucket non è vuoto, la nextvariabile dell'oggetto Entry viene utilizzata per memorizzare questa nuova coppia di valori chiave.

e questo verrà memorizzato come elenco collegato per ogni oggetto che ha lo stesso hashcode, ma un TRIEFY_THRESHOLD viene specificato con il valore 6. quindi dopo questo raggiunge, l'elenco collegato viene convertito nell'albero bilanciato (albero rosso-nero) con il primo elemento come radice.


risposta fantastica (y)
Sudhanshu Gaur

2

Ogni oggetto Entry rappresenta una coppia chiave-valore. Il campo successivo si riferisce ad un altro oggetto Voce se un bucket ha più di 1 Voce.

A volte può succedere che hashCodes per 2 oggetti diversi siano uguali. In questo caso 2 oggetti verranno salvati in un bucket e verranno presentati come LinkedList. Il punto di ingresso è l'oggetto aggiunto più di recente. Questo oggetto si riferisce ad un altro oggetto con il campo successivo e quindi uno. L'ultima voce si riferisce a null. Quando si crea HashMap con il costruttore predefinito

L'array viene creato con dimensioni 16 e bilanciamento del carico predefinito 0,75.

inserisci qui la descrizione dell'immagine

(Fonte)


1

La mappa hash funziona secondo il principio di hashing

Il metodo get (Key k) di HashMap chiama il metodo hashCode sull'oggetto chiave e applica hashValue restituito alla propria funzione hash statica per trovare una posizione bucket (array di supporto) in cui chiavi e valori sono memorizzati sotto forma di una classe nidificata chiamata Entry (Mappa. Ingresso). Quindi hai concluso che dalla riga precedente sia la chiave che il valore sono memorizzati nel bucket come una forma di oggetto Entry. Quindi pensare che solo il valore sia memorizzato nel bucket non è corretto e non darà una buona impressione all'intervistatore.

  • Ogni volta che chiamiamo il metodo get (Key k) sull'oggetto HashMap. Innanzitutto controlla se la chiave è nulla o meno. Nota che in HashMap può esserci solo una chiave null.

Se la chiave è nulla, le chiavi null vengono sempre associate all'hash 0, quindi indicizzano 0.

Se la chiave non è nulla, chiamerà hashfunction sull'oggetto chiave, vedere la riga 4 nel metodo sopra, ovvero key.hashCode (), quindi dopo key.hashCode () restituisce hashValue, la riga 4 appare come

            int hash = hash(hashValue)

e ora applica hashValue restituito nella propria funzione di hashing.

Potremmo chiederci perché stiamo calcolando di nuovo l'hashvalue usando hash (hashValue). La risposta è Difende dalle funzioni hash di scarsa qualità.

Ora l'hashvalue finale viene utilizzato per trovare la posizione del bucket in cui è archiviato l'oggetto Entry. L'oggetto entry viene memorizzato nel bucket in questo modo (hash, chiave, valore, bucketindex)


1

Non entrerò nei dettagli di come funziona HashMap, ma fornirò un esempio in modo da ricordare come funziona HashMap mettendolo in relazione con la realtà.

Abbiamo chiave, valore, codice hash e bucket.

Per qualche tempo, metteremo in relazione ciascuno di essi con quanto segue:

  • Benna -> Una società
  • HashCode -> Indirizzo della società (unico sempre)
  • Valore -> Una casa nella società
  • Chiave -> Indirizzo della casa.

Utilizzando Map.get (chiave):

Stevie vuole arrivare a casa del suo amico (Josse) che vive in una villa in una società VIP, lascia che sia JavaLovers Society. L'indirizzo di Josse è il suo SSN (che è diverso per tutti). C'è un indice mantenuto in cui scopriamo il nome della Società basato su SSN. Questo indice può essere considerato un algoritmo per scoprire l'HashCode.

  • Nome della società SSN
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - BiologyLovers

  1. Questo SSN (chiave) per prima cosa ci dà un HashCode (dalla tabella dell'indice) che non è altro che il nome della Società.
  2. Ora, case multiple possono essere nella stessa società, quindi HashCode può essere comune.
  3. Supponiamo che la Società sia comune per due case, come possiamo identificare a quale casa andremo, sì, usando il tasto (SSN) che non è altro che l'indirizzo della casa

Utilizzo di Map.put (chiave, valore)

Questo trova una società adatta per questo valore trovando il codice hash e quindi il valore viene memorizzato.

Spero che questo aiuti e che sia aperto a modifiche.


0

Sarà una lunga risposta, prendi un drink e continua a leggere ...

L'hashing consiste nel memorizzare una coppia chiave-valore in memoria che può essere letta e scritta più velocemente. Memorizza le chiavi in ​​un array e i valori in un LinkedList.

Diciamo che voglio memorizzare 4 coppie chiave-valore -

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

Quindi per memorizzare le chiavi abbiamo bisogno di un array di 4 elementi. Ora come posso mappare una di queste 4 chiavi su 4 indici di array (0,1,2,3)?

Quindi java trova l'hashCode delle singole chiavi e le associa a un particolare indice di array. Hashcode Formulas è -

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

Hash and girl !! So cosa stai pensando. Il tuo fascino per quel duetto selvaggio potrebbe farti perdere una cosa importante.

Perché java lo moltiplica per 31?

È perché, 31 è un numero primo dispari nella forma 2 ^ 5 - 1. E il primo dispari riduce la possibilità di Hash Collision

Ora come viene mappato questo codice hash su un indice di array?

risposta è Hash Code % (Array length -1) . Quindi “girl”è mappato (3173020 % 3) = 1nel nostro caso. che è il secondo elemento dell'array.

e il valore "ahhan" è memorizzato in un LinkedList associato all'indice di array 1.

HashCollision - Se provi a trovare hasHCodele chiavi “misused”e “horsemints”usando le formule sopra descritte vedrai entrambi darci lo stesso 1069518484. Whooaa !! lezione imparata -

2 oggetti uguali devono avere lo stesso hashCode ma non vi è alcuna garanzia se l'hashCode corrisponde quindi gli oggetti sono uguali. Quindi dovrebbe archiviare entrambi i valori corrispondenti a "abuso" e "horsemints" nel bucket 1 (1069518484% 3).

Ora la mappa hash sembra:

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

Ora se qualche body prova a trovare il valore per la chiave “horsemints”, java troverà rapidamente l'hashCode di esso, lo modellerà e inizierà a cercare il suo valore nella LinkedList corrispondente index 1. Quindi in questo modo non è necessario cercare tutti i 4 indici dell'array, rendendo così più veloce l'accesso ai dati.

Ma aspetta un secondo. ci sono 3 valori in quel LinkedList corrispondente all'indice Array 1, come scopre quale era il valore per i "parametri" chiave?

In realtà ho mentito, quando ho detto che HashMap memorizza i valori in LinkedList.

Memorizza entrambe le coppie chiave-valore come voce della mappa. Quindi in realtà la mappa è simile a questa.

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

Ora puoi vedere Mentre attraversi l'Elenco collegato corrispondente ad ArrayIndex1, in realtà confronta la chiave di ciascuna voce di quell'Elenco collegato con i "nodi" e quando ne trova uno restituisce semplicemente il valore di esso.

Spero ti sia divertito a leggerlo :)


Penso che sia sbagliato: "Memorizza le chiavi in ​​un array e i valori in un LinkedList."
ACV,

ogni elemento nell'elenco per ogni bucket contiene la chiave e il valore nonché il riferimento al nodo successivo.
ACV,

0

Come si dice, un'immagine vale 1000 parole. Dico: un po 'di codice è meglio di 1000 parole. Ecco il codice sorgente di HashMap. Ottieni metodo:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

Quindi diventa chiaro che l'hash viene utilizzato per trovare il "bucket" e il primo elemento viene sempre verificato in quel bucket. In caso contrario, equalsla chiave viene utilizzata per trovare l'elemento effettivo nell'elenco collegato.

Vediamo il put()metodo:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

È leggermente più complicato, ma diventa chiaro che il nuovo elemento viene inserito nella scheda nella posizione calcolata in base all'hash:

i = (n - 1) & hashecco il'indice in cui verrà inserito il nuovo elemento (o è il "secchio"). nè la dimensione tabdell'array (array di "bucket").

Innanzitutto, si tenta di essere inserito come primo elemento in quel "secchio". Se esiste già un elemento, aggiungi un nuovo nodo all'elenco.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.