HashMap
enthält eine bestimmte Anzahl von Eimern. Hiermit wird hashCode
bestimmt, in welchen Eimer diese gelegt werden sollen. Stellen Sie sich der Einfachheit halber als Modul vor.
Wenn unser Hashcode 123456 lautet und wir 4 Eimer haben, wird 123456 % 4 = 0
der Artikel in den ersten Eimer, Eimer 1, verschoben.
Wenn unsere Hashcode-Funktion gut ist, sollte sie eine gleichmäßige Verteilung bieten, damit alle Buckets etwas gleich verwendet werden. In diesem Fall verwendet der Bucket eine verknüpfte Liste zum Speichern der Werte.
Sie können sich jedoch nicht darauf verlassen, dass Leute gute Hash-Funktionen implementieren. Menschen schreiben oft schlechte Hash-Funktionen, was zu einer ungleichmäßigen Verteilung führt. Es ist auch möglich, dass wir mit unseren Eingaben einfach Pech haben.
Je geringer diese Verteilung ist, desto weiter bewegen wir uns von O (1) -Operationen und desto näher kommen wir O (n) -Operationen.
Die Implementierung von Hashmap versucht dies zu mildern, indem einige Buckets in Bäumen anstatt in verknüpften Listen organisiert werden, wenn die Buckets zu groß werden. Dafür ist da TREEIFY_THRESHOLD = 8
. Wenn ein Eimer mehr als acht Elemente enthält, sollte er zu einem Baum werden.
Dieser Baum ist ein rot-schwarzer Baum. Es wird zuerst nach Hash-Code sortiert. Wenn die Hash-Codes identisch sind, wird die compareTo
Methode verwendet, Comparable
ob die Objekte diese Schnittstelle implementieren, andernfalls der Identitäts-Hash-Code.
Wenn Einträge aus der Karte entfernt werden, kann sich die Anzahl der Einträge im Bucket verringern, sodass diese Baumstruktur nicht mehr erforderlich ist. Dafür ist das da UNTREEIFY_THRESHOLD = 6
. Wenn die Anzahl der Elemente in einem Bucket unter sechs fällt, können Sie auch wieder eine verknüpfte Liste verwenden.
Endlich gibt es die MIN_TREEIFY_CAPACITY = 64
.
Wenn eine Hash-Map größer wird, ändert sie automatisch ihre Größe, um mehr Buckets zu haben. Wenn wir eine kleine Hash-Karte haben, ist die Wahrscheinlichkeit, dass wir sehr volle Eimer bekommen, ziemlich hoch, da wir nicht so viele verschiedene Eimer haben, in die wir Sachen stecken können. Es ist viel besser, eine größere Hash-Karte mit mehr Eimern zu haben, die weniger voll sind. Diese Konstante besagt im Grunde, dass wir nicht damit beginnen sollen, Eimer in Bäume zu verwandeln, wenn unsere Hash-Karte sehr klein ist. Stattdessen sollte die Größe geändert werden, damit sie zuerst größer wird.
Um Ihre Frage zum Leistungsgewinn zu beantworten, wurden diese Optimierungen hinzugefügt, um den schlimmsten Fall zu verbessern . Ich spekuliere nur, aber Sie würden aufgrund dieser Optimierungen wahrscheinlich nur dann eine spürbare Leistungsverbesserung feststellen, wenn Ihre hashCode
Funktion nicht sehr gut wäre.
String
haben einen weitaus größeren Wertebereich als derint
Hashcode, daher sind Kollisionen unvermeidbar. Jetzt hängt es von den tatsächlichen Werten ab, wie die tatsächlichenString
s, die Sie in die Karte einfügen, ob Sie eine gleichmäßige Verteilung erhalten oder nicht. Eine schlechte Verteilung kann das Ergebnis von Pech sein.java.lang.String
hat eine deterministische, nicht kryptografischehashCode
, so dass Angreifer trivial unterschiedliche Strings mit kollidierenden Hashcodes erstellen können. Vor dieser Optimierung konnte dies HashMap-Operationen auf O (n) -Zeit herabsetzen, jetzt werden sie nur noch auf O (log (n)) herabgesetzt.if the objects implement that interface, else the identity hash code.
ich habe nach diesem anderen Teil gesucht.MIN_TREEIFY_CAPACITY
. Bedeutet dies "Sobald wir einen Schlüssel einfügen, der in den Bucket gehasht werden soll, der bereits 8 (TREEIFY_THRESHOLD
) Schlüssel enthält, und wenn bereits 64 (MIN_TREEIFY_CAPACITY
) Schlüssel vorhanden sindHashMap
, wird die verknüpfte Liste dieses Buckets in einen ausgeglichenen Baum konvertiert."Einfacher ausgedrückt (so viel ich könnte einfacher) + einige weitere Details.
Diese Eigenschaften hängen von vielen internen Dingen ab, deren Verständnis sehr cool wäre - bevor Sie direkt zu ihnen wechseln.
TREEIFY_THRESHOLD -> Wenn ein einzelner Bucket dies erreicht (und die Gesamtzahl überschreitet
MIN_TREEIFY_CAPACITY
), wird er in einen perfekt ausbalancierten rot / schwarzen Baumknoten umgewandelt . Warum? Wegen der Suchgeschwindigkeit. Denken Sie anders darüber nach:Ein Intro zum nächsten Thema. Warum ist die Anzahl der Behälter / Eimer immer eine Zweierpotenz ? Mindestens zwei Gründe: Schneller als Modulo-Betrieb und Modulo bei negativen Zahlen sind negativ. Und Sie können einen Eintrag nicht in einen "negativen" Eimer legen:
Stattdessen wird anstelle von Modulo ein netter Trick verwendet:
Das ist semantisch dasselbe wie Modulo-Operation. Die unteren Bits bleiben erhalten. Dies hat eine interessante Konsequenz, wenn Sie Folgendes tun:
Hier kommt das Multiplizieren der Eimer ins Spiel. Unter bestimmten Bedingungen (die genaue Erklärung würde viel Zeit in Anspruch nehmen ) werden die Eimer doppelt so groß. Warum? Wenn die Größe der Eimer verdoppelt wird, kommt noch ein Bit ins Spiel .
Als solches wird dieser Prozess als erneutes Hashing bezeichnet. Dies könnte langsam werden. Das heißt (für Leute, die sich interessieren), da HashMap "gescherzt" wird als: schnell, schnell, schnell, langsam . Es gibt andere Implementierungen - Suche pausenlose Hashmap ...
Jetzt kommt UNTREEIFY_THRESHOLD nach dem erneuten Hashing ins Spiel. Zu diesem Zeitpunkt werden einige Einträge möglicherweise von diesen Bins zu anderen verschoben (sie fügen der
(n-1)&hash
Berechnung ein weiteres Bit hinzu - und als solches möglicherweise zu anderen Buckets), und dies kann erreicht werdenUNTREEIFY_THRESHOLD
. An dieser Stelle zahlt es sich nicht aus, den Mülleimer alsred-black tree node
, sondern alsLinkedList
, wie zu haltenMIN_TREEIFY_CAPACITY ist die Mindestanzahl von Buckets, bevor ein bestimmter Bucket in einen Baum umgewandelt wird.
quelle
TreeNode
ist eine alternative Möglichkeit, die Einträge zu speichern, die zu einem einzelnen Bin des gehörenHashMap
. In älteren Implementierungen wurden die Einträge eines Fachs in einer verknüpften Liste gespeichert. Wenn in Java 8 die Anzahl der Einträge in einem Bin einen Schwellenwert (TREEIFY_THRESHOLD
) überschritten hat , werden sie in einer Baumstruktur anstelle der ursprünglichen verknüpften Liste gespeichert. Dies ist eine Optimierung.Aus der Implementierung:
quelle
TREEIFY_THRESHOLD
UND bestehen, beträgt die Gesamtzahl der Behälter mindestensMIN_TREEIFY_CAPACITY
. Ich habe versucht, das in meiner Antwort zu behandeln ...Sie müssten es visualisieren: Angenommen, es gibt einen Klassenschlüssel, bei dem nur die Funktion hashCode () überschrieben wird, um immer den gleichen Wert zurückzugeben
und dann füge ich woanders 9 Einträge in eine HashMap ein, wobei alle Schlüssel Instanzen dieser Klasse sind. z.B
Die Baumdurchquerung ist schneller {O (log n)} als die LinkedList {O (n)}, und wenn n wächst, wird der Unterschied signifikanter.
quelle
compareTo
von verwendenComparable
.identityHashCode
ist ein weiterer Mechanismus, den es verwendet.Key
nicht implementiertComparable
,identityHashCode
wird verwendet :)Die Änderung in der HashMap-Implementierung wurde mit JEP-180 hinzugefügt . Der Zweck war:
Reine Leistung ist jedoch nicht der einzige Gewinn. Es verhindert auch einen HashDoS-Angriff , falls eine Hash-Map zum Speichern von Benutzereingaben verwendet wird, da der rot-schwarze Baum , der zum Speichern von Daten im Bucket verwendet wird, die Komplexität der Einfügung im ungünstigsten Fall in O (log n) aufweist. Der Baum wird verwendet, nachdem bestimmte Kriterien erfüllt sind - siehe Eugenes Antwort .
quelle
Um die interne Implementierung von Hashmap zu verstehen, müssen Sie das Hashing verstehen. Hashing in seiner einfachsten Form ist eine Möglichkeit, einen eindeutigen Code für eine Variable / ein Objekt zuzuweisen, nachdem eine Formel / ein Algorithmus auf seine Eigenschaften angewendet wurde.
Eine echte Hash-Funktion muss dieser Regel folgen -
„Die Hash-Funktion sollte jedes Mal den gleichen Hash-Code zurückgeben, wenn die Funktion auf gleiche oder gleiche Objekte angewendet wird. Mit anderen Worten, zwei gleiche Objekte müssen konsistent denselben Hash-Code erzeugen. “
quelle