Wie behandelt eine Java HashMap verschiedene Objekte mit demselben Hashcode?


223

Nach meinem Verständnis denke ich:

  1. Es ist völlig legal, dass zwei Objekte denselben Hashcode haben.
  2. Wenn zwei Objekte gleich sind (mit der Methode equals ()), haben sie denselben Hashcode.
  3. Wenn zwei Objekte nicht gleich sind, können sie nicht denselben Hashcode haben

Hab ich recht?

Wenn das nun stimmt, habe ich folgende Frage: Der verwendet HashMapintern den Hashcode des Objekts. Wenn also zwei Objekte denselben Hashcode haben können, wie kann dann verfolgt werden, HashMapwelchen Schlüssel sie verwenden?

Kann jemand erklären, wie der HashMapintern den Hashcode des Objekts verwendet?


29
Für den Datensatz: # 1 und # 2 sind korrekt, # 3 ist falsch: Zwei Objekte, die nicht gleich sind, haben möglicherweise denselben Hash-Code.
Joachim Sauer

6
# 1 und # 3 sind sogar widersprüchlich
Delfic

Wenn # 2 nicht befolgt wird, ist die Implementierung von equals () (oder wohl der hashCode ()) falsch.
Joachim Sauer

Antworten:


346

Eine Hashmap funktioniert folgendermaßen (dies ist ein wenig vereinfacht, zeigt aber den grundlegenden Mechanismus):

Es verfügt über eine Reihe von "Buckets", in denen Schlüssel-Wert-Paare gespeichert werden. Jeder Bucket hat eine eindeutige Nummer - das ist es, was den Bucket identifiziert. Wenn Sie ein Schlüssel-Wert-Paar in die Karte einfügen, zeigt die Hashmap den Hash-Code des Schlüssels an und speichert das Paar in dem Bucket, dessen Kennung der Hash-Code des Schlüssels ist. Beispiel: Der Hash-Code des Schlüssels lautet 235 -> das Paar wird in Bucket Nummer 235 gespeichert. (Beachten Sie, dass ein Bucket mehr als ein Schlüssel-Wert-Paar speichern kann.)

Wenn Sie einen Wert in der Hashmap nachschlagen, indem Sie ihm einen Schlüssel geben, wird zuerst der Hash-Code des von Ihnen angegebenen Schlüssels angezeigt. Die Hashmap untersucht dann den entsprechenden Bucket und vergleicht den von Ihnen angegebenen Schlüssel mit den Schlüsseln aller Paare im Bucket, indem Sie sie mit vergleichen equals().

Jetzt können Sie sehen, wie effizient dies ist, um Schlüssel-Wert-Paare in einer Karte nachzuschlagen: Durch den Hash-Code des Schlüssels weiß die Hashmap sofort, in welchem ​​Bucket sie suchen muss, sodass sie nur testen muss, was sich in diesem Bucket befindet.

Wenn Sie sich den obigen Mechanismus ansehen, können Sie auch sehen, welche Anforderungen an die hashCode()und equals()Methoden der Schlüssel erforderlich sind :

  • Wenn zwei Schlüssel gleich (sind equals()Renditen , truewenn man sie vergleichen), ihre hashCode()Methode müssen die gleiche Anzahl zurück. Wenn Schlüssel dagegen verstoßen, werden gleichwertige Schlüssel möglicherweise in verschiedenen Buckets gespeichert, und die Hashmap kann keine Schlüssel-Wert-Paare finden (da sie im selben Bucket angezeigt werden).

  • Wenn zwei Schlüssel unterschiedlich sind, spielt es keine Rolle, ob ihre Hash-Codes gleich sind oder nicht. Sie werden im selben Bucket gespeichert, wenn ihre Hash-Codes identisch sind. In diesem Fall werden sie anhand der Hashmap equals()voneinander unterschieden.


4
Sie haben geschrieben "und die Hashmap könnte keine Schlüssel-Wert-Paare finden (weil sie im selben Bucket aussehen wird)." Können Sie erklären, dass es im selben Bucket aussehen wird, wenn diese beiden gleichen Objekte t1 und t2 sind und sie gleich sind und t1 und t2 Hashcodes h1 bzw. h2 haben. Also t1.equals (t2) = true und h1! = H2 Wenn also die Hashmap nach t1 suchen würde, würde sie im Bucket h1 und nach t2 im Bucket t2 suchen?
Geek

19
Wenn zwei Schlüssel gleich sind, ihre hashCode()Methode jedoch unterschiedliche Hash-Codes zurückgibt, verstoßen die Methoden equals()und hashCode()der Schlüsselklasse gegen den Vertrag, und Sie erhalten seltsame Ergebnisse, wenn Sie diese Schlüssel in a verwenden HashMap.
Jesper

Jeder Bucket kann mehrere Schlüsselwertpaare haben, die intern als verknüpfte Liste verwendet werden. Aber meine Verwirrung ist - was ist Eimer hier? Welche Datenstruktur wird intern verwendet? Gibt es eine Verbindung zwischen Eimern?
Ankit Sharma

1
@AnkitSharma Wenn Sie wirklich alle Details wissen möchten, suchen Sie den Quellcode von HashMap, den Sie in der Datei src.zipin Ihrem JDK-Installationsverzeichnis finden.
Jesper

1
@ 1290 Die einzige Beziehung zwischen den Schlüsseln im selben Bucket besteht darin, dass sie denselben Hash-Code haben.
Jesper

88

Ihre dritte Behauptung ist falsch.

Es ist völlig legal, dass zwei ungleiche Objekte denselben Hash-Code haben. Es wird von HashMapals "First-Pass-Filter" verwendet, damit die Karte mögliche Einträge mit dem angegebenen Schlüssel schnell finden kann . Die Schlüssel mit demselben Hashcode werden dann auf Gleichheit mit dem angegebenen Schlüssel getestet.

Sie möchten nicht, dass zwei ungleiche Objekte nicht denselben Hash-Code haben, da Sie sonst auf 2 32 mögliche Objekte beschränkt wären . (Dies würde auch bedeuten, dass verschiedene Typen nicht einmal die Felder eines Objekts zum Generieren von Hash-Codes verwenden könnten, da andere Klassen denselben Hash generieren könnten.)


6
Wie bist du zu 2 ^ 32 möglichen Objekten gekommen?
Geek

5
Ich bin spät dran, aber für diejenigen, die sich immer noch fragen: Ein Hashcode in Java ist ein int, und ein int hat 2 ^ 32 mögliche Werte
Xerus

69

HashMap-Strukturdiagramm

HashMapist ein Array von EntryObjekten.

Betrachten Sie HashMapals nur ein Array von Objekten.

Schauen Sie sich an, was das Objectist:

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

Jedes EntryObjekt repräsentiert ein Schlüssel-Wert-Paar. Das Feld nextbezieht sich auf ein anderes EntryObjekt, wenn ein Bucket mehr als ein Objekt hat Entry.

Manchmal kann es vorkommen, dass Hash-Codes für zwei verschiedene Objekte gleich sind. In diesem Fall werden zwei Objekte in einem Bucket gespeichert und als verknüpfte Liste angezeigt. Der Einstiegspunkt ist das zuletzt hinzugefügte Objekt. Dieses Objekt bezieht sich auf ein anderes Objekt mit dem nextFeld und so weiter. Der letzte Eintrag bezieht sich auf null.

Wenn Sie eine HashMapmit dem Standardkonstruktor erstellen

HashMap hashMap = new HashMap();

Das Array wird mit der Größe 16 und einem Standard-Lastausgleich von 0,75 erstellt.

Hinzufügen eines neuen Schlüssel-Wert-Paares

  1. Berechnen Sie den Hashcode für den Schlüssel
  2. Berechnen Sie die Position, hash % (arrayLength-1)an der das Element platziert werden soll (Bucket-Nummer)
  3. Wenn Sie versuchen, einen Wert mit einem bereits gespeicherten Schlüssel hinzuzufügen HashMap, wird der Wert überschrieben.
  4. Andernfalls wird dem Bucket ein Element hinzugefügt.

Wenn der Eimer bereits mindestens ein Element enthält, wird ein neues hinzugefügt und an der ersten Position des Eimers platziert. Sein nextFeld bezieht sich auf das alte Element.

Streichung

  1. Berechnen Sie den Hashcode für den angegebenen Schlüssel
  2. Eimernummer berechnen hash % (arrayLength-1)
  3. Holen Sie sich einen Verweis auf das erste Eintragsobjekt im Bucket und iterieren Sie mit der Methode equals über alle Einträge im angegebenen Bucket. Irgendwann werden wir das Richtige finden Entry. Wenn ein gewünschtes Element nicht gefunden wird, kehren Sie zurücknull

3
Das ist falsch hash % (arrayLength-1), wäre es hash % arrayLength. Aber es ist tatsächlich so hash & (arrayLength-1) . Das heißt, weil es Potenzen von zwei ( 2^n) für die Arraylänge verwendet und nniedrigstwertige Bits benötigt.
Weston

Ich denke, es ist kein Array von Entity-Objekten, sondern ein Array von LinkedList / Tree. Und jeder Baum hat intern Entitätsobjekte.
Mudit Bhaintwal

@ Shevchyk Warum speichern wir Schlüssel und Hash? Was nützen sie? Verschwenden wir hier nicht Erinnerung?
Roottraveller

Hashset verwendet intern Hashmap. Gilt das Hinzufügen und Löschen von Hashmap-Regeln für Hashset?
Überaustausch

2
@weston nicht nur das, hashCode ist ein, intwas natürlich negativ sein kann, Modulo auf einer negativen Zahl zu tun, gibt Ihnen eine negative Zahl
Eugene

35

Hervorragende Informationen finden Sie unter http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html

Zusammenfassen:

HashMap arbeitet nach dem Prinzip des Hashings

put (Schlüssel, Wert): HashMap speichert sowohl Schlüssel als auch Wertobjekt als Map.Entry. Hashmap wendet Hashcode (Schlüssel) an, um den Bucket zu erhalten. Bei einer Kollision verwendet HashMap LinkedList zum Speichern des Objekts.

get (key): HashMap verwendet den Hashcode von Key Object, um die Position des Buckets zu ermitteln, und ruft dann die Methode keys.equals () auf, um den richtigen Knoten in LinkedList zu identifizieren und das zugehörige Wertobjekt für diesen Schlüssel in Java HashMap zurückzugeben.


3
Ich fand die Antwort von Jasper besser. Ich hatte das Gefühl, dass der Blog mehr dem Umgang mit Interviews dient als dem Verständnis des Konzepts
Narendra N

@ NarendraN Ich stimme dir zu.
Abhijit Gaikwad

22

Hier ist eine grobe Beschreibung des HashMapMechanismus für die Java 8Version (er kann sich geringfügig von Java 6 unterscheiden) .


Datenstrukturen

  • Hash-Tabelle Der
    Hash-Wert wird über den hash()Schlüssel on berechnet und entscheidet, welcher Bucket der Hashtabelle für einen bestimmten Schlüssel verwendet werden soll.
  • Verknüpfte Liste (einzeln)
    Wenn die Anzahl der Elemente in einem Bucket gering ist, wird eine einzeln verknüpfte Liste verwendet.
  • Rot-Schwarz-Baum
    Wenn die Anzahl der Elemente in einem Eimer groß ist, wird ein Rot-Schwarz-Baum verwendet.

Klassen (intern)

  • Map.Entry
    Stellen Sie eine einzelne Entität in der Karte dar, die Schlüssel- / Wertentität.
  • HashMap.Node
    Verknüpfte Listenversion des Knotens.

    Es könnte darstellen:

    • Ein Hash-Eimer.
      Weil es eine Hash-Eigenschaft hat.
    • Ein Knoten in einer einfach verknüpften Liste (also auch Leiter der verknüpften Liste ) .
  • HashMap.TreeNode
    Baumversion des Knotens.

Felder (intern)

  • Node[] table
    Die Bucket-Tabelle (Kopf der verknüpften Listen).
    Wenn ein Bucket keine Elemente enthält, ist er null und nimmt daher nur Platz für eine Referenz.
  • Set<Map.Entry> entrySet Satz von Entitäten.
  • int size
    Anzahl der Entitäten.
  • float loadFactor
    Geben Sie an, wie voll die Hash-Tabelle zulässig ist, bevor Sie die Größe ändern.
  • int threshold
    Die nächste Größe, bei der die Größe geändert werden soll.
    Formel:threshold = capacity * loadFactor

Methoden (intern)

  • int hash(key)
    Berechnen Sie den Hash anhand des Schlüssels.
  • Wie ordne ich Hash dem Bucket zu?
    Verwenden Sie folgende Logik:

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

Über die Kapazität

In der Hash-Tabelle bedeutet Kapazität die Anzahl der Buckets, von denen sie stammen könnte table.length.
Auch könnte über thresholdund berechnet werden loadFactor, so dass kein Klassenfeld definiert werden muss.

Könnte die effektive Kapazität erhalten über: capacity()


Operationen

  • Entität nach Schlüssel suchen.
    Suchen Sie zuerst den Bucket anhand des Hashwerts und wiederholen Sie dann die verknüpfte Liste oder den sortierten Baum.
  • Entität mit Schlüssel hinzufügen.
    Suchen Sie zuerst den Bucket nach dem Hashwert des Schlüssels.
    Versuchen Sie dann, den Wert zu finden:
    • Wenn gefunden, ersetzen Sie den Wert.
    • Andernfalls fügen Sie am Anfang der verknüpften Liste einen neuen Knoten hinzu oder fügen Sie ihn in einen sortierten Baum ein.
  • Größe ändern
    Wenn thresholderreicht, wird die Kapazität der Hashtabelle ( table.length) verdoppelt , und anschließend werden alle Elemente erneut gehasht , um die Tabelle neu zu erstellen .
    Dies könnte eine teure Operation sein.

Performance

  • get & put
    Zeitliche Komplexität ist O(1), weil:
    • Auf den Bucket wird somit über den Array-Index zugegriffen O(1).
    • Die verknüpfte Liste in jedem Bucket ist von geringer Länge und kann daher als angezeigt werden O(1).
    • Die Baumgröße ist ebenfalls begrenzt, da sie die Kapazität erweitert und den Hash erneut erhöht, wenn sich die Anzahl der Elemente erhöht. Daher kann dies als O(1)nicht angesehen werden O(log N).

Können Sie bitte ein Beispiel geben? Wie hat die Zeit Komplexität O (1)
Jitendra

@jsroyal Dies könnte die Komplexität klarer erklären: en.wikipedia.org/wiki/Hash_table . Kurz gesagt: Das Finden des Ziel-Buckets ist O (1), da Sie es anhand des Index in einem Array finden. dann ist innerhalb eines Buckets die Menge der Elemente klein und im Durchschnitt eine konstante Anzahl trotz der Gesamtzahl der Elemente in der gesamten Hashtabelle, so dass die Suche nach dem Zielelement innerhalb eines Buckets auch O (1) ist; somit ist O (1) + O (1) = O (1).
Eric Wang

14

Der Hashcode bestimmt, welcher Bucket für die Hashmap überprüft werden soll. Befindet sich mehr als ein Objekt im Bucket, wird eine lineare Suche durchgeführt, um herauszufinden, welches Element im Bucket dem gewünschten Element entspricht (unter Verwendung der equals()Methode).

Mit anderen Worten, wenn Sie einen perfekten Hashcode haben und der Hashmap-Zugriff konstant ist, müssen Sie niemals durch einen Bucket iterieren (technisch gesehen müssten Sie auch MAX_INT-Buckets haben, die Java-Implementierung kann einige Hash-Codes im selben Bucket gemeinsam nutzen Platzbedarf reduzieren). Wenn Sie den schlechtesten Hashcode haben (gibt immer die gleiche Nummer zurück), wird Ihr Hashmap-Zugriff linear, da Sie jedes Element auf der Karte durchsuchen müssen (alle befinden sich im selben Bucket), um das zu erhalten, was Sie möchten.

Meistens ist ein gut geschriebener Hashcode nicht perfekt, aber einzigartig genug, um Ihnen mehr oder weniger konstanten Zugriff zu ermöglichen.


11

Sie irren sich in Punkt drei. Zwei Einträge können denselben Hashcode haben, sind jedoch nicht gleich. Schauen Sie sich die Implementierung von HashMap.get aus dem OpenJdk an . Sie können sehen, dass überprüft wird, ob die Hashes gleich und die Schlüssel gleich sind. Wäre Punkt drei wahr, wäre es nicht notwendig zu überprüfen, ob die Schlüssel gleich sind. Der Hash-Code wird vor dem Schlüssel verglichen, da ersterer ein effizienterer Vergleich ist.

Wenn Sie mehr darüber erfahren möchten, lesen Sie den Wikipedia-Artikel zur Open Addressing-Kollisionsauflösung , der meiner Meinung nach der Mechanismus ist, den die OpenJdk-Implementierung verwendet. Dieser Mechanismus unterscheidet sich geringfügig von dem "Bucket" -Ansatz, den eine der anderen Antworten erwähnt.


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

Hier sehen wir also, dass wenn beide Objekte S1 und S2 unterschiedliche Inhalte haben, wir ziemlich sicher sind, dass unsere überschriebene Hashcode-Methode für beide Objekte unterschiedliche Hashcodes (116232,11601) generiert. JETZT, da es verschiedene Hash-Codes gibt, wird es nicht einmal die Mühe machen, die EQUALS-Methode aufzurufen. Weil ein anderer Hashcode UNTERSCHIEDLICHE Inhalte in einem Objekt GARANTIERT.

    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

Zwei Objekte sind gleich, was bedeutet, dass sie denselben Hashcode haben, aber nicht umgekehrt.

2 gleiche Objekte ------> sie haben den gleichen Hashcode

2 Objekte haben den gleichen Hashcode ---- xxxxx -> sie sind NICHT gleich

Java 8 Update in HashMap-

Sie führen diese Operation in Ihrem Code aus -

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

Angenommen, Ihr Hashcode wird für beide Schlüssel zurückgegeben "old"und "very-old"ist gleich. Was wird dann passieren?

myHashMapist eine HashMap, und nehmen Sie an, dass Sie ihre Kapazität anfangs nicht angegeben haben. Die Standardkapazität gemäß Java beträgt also 16. Sobald Sie die Hashmap mit dem neuen Schlüsselwort initialisiert haben, wurden 16 Buckets erstellt. jetzt, als Sie die erste Anweisung ausgeführt haben-

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

dann wird der Hashcode für "old"berechnet, und da der Hashcode auch eine sehr große Ganzzahl sein kann, hat Java dies intern getan - (Hash ist hier Hashcode und >>> ist Rechtsverschiebung)

hash XOR hash >>> 16

Um ein größeres Bild zu erhalten, wird ein Index zurückgegeben, der zwischen 0 und 15 liegt. Jetzt Ihr Schlüsselwertpaar "old"und"old-value" würde in den Entry Schlüssel und den Wert des Objekts Instanzvariable umgewandelt werden. und dann wird dieses Eintragsobjekt im Bucket gespeichert, oder Sie können sagen, dass dieses Eintragsobjekt an einem bestimmten Index gespeichert wird.

FYI-Entry ist eine Klasse in Map Interface-Map.Entry mit dieser Signatur / Definition

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

jetzt, wenn Sie die nächste Anweisung ausführen -

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

und "very-old"gibt denselben Hashcode wie aus "old", sodass dieses neue Schlüsselwertpaar erneut an denselben Index oder denselben Bucket gesendet wird. Da dieser Bucket jedoch nicht leer ist, wird die nextVariable des Entry-Objekts zum Speichern dieses neuen Schlüsselwertpaars verwendet.

und dies wird als verknüpfte Liste für jedes Objekt gespeichert, das denselben Hashcode hat, aber ein TRIEFY_THRESHOLD mit dem Wert 6 angegeben wird. Nachdem dies erreicht ist, wird die verknüpfte Liste in den ausgeglichenen Baum (rot-schwarzer Baum) mit dem ersten Element als konvertiert Wurzel.


fantastische Antwort (y)
Sudhanshu Gaur

2

Jedes Eintragsobjekt repräsentiert ein Schlüssel-Wert-Paar. Das nächste Feld bezieht sich auf ein anderes Eintragsobjekt, wenn ein Bucket mehr als einen Eintrag hat.

Manchmal kann es vorkommen, dass hashCodes für 2 verschiedene Objekte gleich sind. In diesem Fall werden 2 Objekte in einem Bucket gespeichert und als LinkedList dargestellt. Der Einstiegspunkt ist ein kürzlich hinzugefügtes Objekt. Dieses Objekt bezieht sich auf ein anderes Objekt mit dem nächsten Feld und damit einem. Letzter Eintrag bezieht sich auf null. Wenn Sie HashMap mit dem Standardkonstruktor erstellen

Das Array wird mit der Größe 16 und einem Standard-Lastausgleich von 0,75 erstellt.

Geben Sie hier die Bildbeschreibung ein

(Quelle)


1

Hash Map arbeitet nach dem Prinzip des Hashings

Die HashMap-Methode get (Key k) ruft die hashCode-Methode für das Schlüsselobjekt auf und wendet den zurückgegebenen hashValue auf seine eigene statische Hash-Funktion an, um einen Bucket-Speicherort (Backing-Array) zu finden, in dem Schlüssel und Werte in Form einer verschachtelten Klasse namens Entry (Map) gespeichert sind. Eintrag). Sie haben also aus der vorherigen Zeile den Schluss gezogen, dass sowohl Schlüssel als auch Wert als eine Art Eingabeobjekt im Bucket gespeichert sind. Der Gedanke, dass nur Wert im Eimer gespeichert ist, ist also nicht korrekt und hinterlässt beim Interviewer keinen guten Eindruck.

  • Immer wenn wir die Methode get (Key k) für das HashMap-Objekt aufrufen. Zuerst wird überprüft, ob der Schlüssel null ist oder nicht. Beachten Sie, dass es in HashMap nur einen Nullschlüssel geben kann.

Wenn der Schlüssel null ist, werden Nullschlüssel immer dem Hash 0 zugeordnet, also dem Index 0.

Wenn key dann nicht null ist, ruft es Hashfunktion für das Schlüsselobjekt auf, siehe Zeile 4 in der obigen Methode, dh key.hashCode (). Nachdem key.hashCode () hashValue zurückgegeben hat, sieht Zeile 4 so aus

            int hash = hash(hashValue)

und jetzt wendet es den zurückgegebenen hashValue auf seine eigene Hashing-Funktion an.

Wir fragen uns vielleicht, warum wir den Hashwert erneut mit Hash (hashValue) berechnen. Die Antwort lautet: Es schützt vor Hash-Funktionen von schlechter Qualität.

Jetzt wird der endgültige Hashwert verwendet, um den Bucket-Speicherort zu ermitteln, an dem das Entry-Objekt gespeichert ist. Das Eintragsobjekt wird wie folgt im Bucket gespeichert (Hash, Schlüssel, Wert, Bucketindex).


1

Ich werde nicht näher auf die Funktionsweise von HashMap eingehen, sondern ein Beispiel geben, damit wir uns daran erinnern können, wie HashMap funktioniert, indem wir es mit der Realität in Beziehung setzen.

Wir haben Schlüssel, Wert, HashCode und Bucket.

Für einige Zeit werden wir jeden von ihnen mit folgendem in Verbindung bringen:

  • Eimer -> Eine Gesellschaft
  • HashCode -> Adresse der Gesellschaft (immer eindeutig)
  • Wert -> Ein Haus in der Gesellschaft
  • Schlüssel -> Hausadresse.

Verwenden von Map.get (Schlüssel):

Stevie möchte zum Haus seines Freundes (Josse) gelangen, der in einer Villa in einer VIP-Gesellschaft lebt. Lassen Sie es die JavaLovers Society sein. Josse's Adresse ist seine SSN (die für jeden anders ist). Es wird ein Index geführt, in dem wir den Namen der Gesellschaft basierend auf SSN herausfinden. Dieser Index kann als Algorithmus zum Ermitteln des HashCodes betrachtet werden.

  • Name der SSN-Gesellschaft
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - BiologyLovers

  1. Diese SSN (Schlüssel) gibt uns zuerst einen HashCode (aus der Indextabelle), der nichts anderes als der Name der Gesellschaft ist.
  2. Jetzt können sich mehrere Häuser in derselben Gesellschaft befinden, sodass der HashCode häufig verwendet werden kann.
  3. Angenommen, die Gesellschaft ist für zwei Häuser gleich. Wie können wir ermitteln, zu welchem ​​Haus wir gehen, ja, indem wir den Schlüssel (SSN) verwenden, der nichts anderes als die Hausadresse ist

Verwenden von Map.put (Schlüssel, Wert)

Dies findet eine geeignete Gesellschaft für diesen Wert, indem der HashCode gefunden wird und dann der Wert gespeichert wird.

Ich hoffe das hilft und das ist offen für Änderungen.


0

Es wird eine lange Antwort sein, etwas trinken und weiterlesen ...

Beim Hashing geht es darum, ein Schlüssel-Wert-Paar im Speicher zu speichern, das schneller gelesen und geschrieben werden kann. Es speichert Schlüssel in einem Array und Werte in einer LinkedList.

Nehmen wir an, ich möchte 4 Schlüsselwertpaare speichern -

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

Um die Schlüssel zu speichern, benötigen wir ein Array mit 4 Elementen. Wie ordne ich nun einen dieser 4 Schlüssel 4 Array-Indizes (0,1,2,3) zu?

Java findet also den HashCode einzelner Schlüssel und ordnet sie einem bestimmten Array-Index zu. Hashcode-Formeln sind -

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 und Mädchen !! Ich weiß was du denkst. Ihre Faszination für dieses wilde Duett könnte dazu führen, dass Sie eine wichtige Sache verpassen.

Warum Java multipliziert es mit 31?

Es ist, weil 31 eine ungerade Primzahl in der Form 2 ^ 5 - 1 ist. Und ungerade Primzahlen verringern die Wahrscheinlichkeit einer Hash-Kollision

Wie wird dieser Hash-Code nun einem Array-Index zugeordnet?

Antwort ist , Hash Code % (Array length -1) . Ist “girl”also (3173020 % 3) = 1in unserem Fall abgebildet . Das ist das zweite Element des Arrays.

und der Wert "ahhan" wird in einer LinkedList gespeichert, die dem Array-Index 1 zugeordnet ist.

HashCollision - Wenn Sie versuchen, hasHCodedie Schlüssel zu finden “misused”und “horsemints”die oben beschriebenen Formeln zu verwenden, sehen Sie, dass beide uns dasselbe geben 1069518484. Whooaa !! Lektion gelernt -

2 gleiche Objekte müssen denselben Hashcode haben, es gibt jedoch keine Garantie dafür, dass die Objekte gleich sind, wenn der Hashcode übereinstimmt. Daher sollten beide Werte, die "missbraucht" und "Horsemints" entsprechen, in Bucket 1 (1069518484% 3) gespeichert werden.

Jetzt sieht die Hash-Karte aus wie -

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

Wenn nun ein Körper versucht, den Wert für den Schlüssel zu finden “horsemints”, findet Java schnell den Hashcode des Schlüssels , moduliert ihn und beginnt mit der Suche nach seinem Wert in der entsprechenden LinkedList index 1. Auf diese Weise müssen wir nicht alle 4 Array-Indizes durchsuchen, um den Datenzugriff zu beschleunigen.

Aber warte, eine Sekunde. Es gibt 3 Werte in dieser linkedList, die dem Array-Index 1 entsprechen. Wie wird herausgefunden, welcher der Werte für die Schlüssel-Horsemints war?

Eigentlich habe ich gelogen, als ich sagte, dass HashMap nur Werte in LinkedList speichert.

Es speichert beide Schlüsselwertpaare als Karteneintrag. Map sieht also tatsächlich so aus.

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

Jetzt können Sie sehen, dass beim Durchlaufen der mit ArrayIndex1 entsprechenden verknüpften Liste tatsächlich der Schlüssel jedes Eintrags mit dieser verknüpften Liste mit „Horsemints“ verglichen wird. Wenn eine gefunden wird, wird nur der Wert zurückgegeben.

Hoffe du hattest Spaß beim Lesen :)


Ich denke, das ist falsch: "Es speichert Schlüssel in einem Array und Werte in einer LinkedList."
ACV

Jedes Element in der Liste für jeden Bucket enthält den Schlüssel und den Wert sowie den Verweis auf den nächsten Knoten.
ACV

0

Ein Bild sagt mehr als 1000 Worte. Ich sage: Ein Code ist besser als 1000 Wörter. Hier ist der Quellcode von HashMap. Methode abrufen:

/**
     * 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;
    }

So wird klar, dass Hash verwendet wird, um den "Bucket" zu finden, und das erste Element immer in diesem Bucket überprüft wird. Wenn nicht, wird equalsder Schlüssel verwendet, um das tatsächliche Element in der verknüpften Liste zu finden.

Schauen wir uns die put()Methode an:

  /**
     * 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;
    }

Es ist etwas komplizierter, aber es wird deutlich, dass das neue Element an der Position, die anhand des Hash berechnet wurde, in die Registerkarte eingefügt wird:

i = (n - 1) & hashHier iist der Index, in den das neue Element eingefügt wird (oder es ist der "Bucket"). nist die Größe des tabArrays (Array von "Buckets").

Zunächst wird versucht, als erstes Element in diesen "Eimer" eingefügt zu werden. Wenn bereits ein Element vorhanden ist, fügen Sie der Liste einen neuen Knoten hinzu.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.