web-dev-qa-db-de.com

Warum verwendet Java's hashCode () in String 31 als Multiplikator?

Gemäß der Java-Dokumentation wird der Hash-Code für ein String-Objekt wie folgt berechnet:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

mit int arithmetic, wobei s[i] der .__ ist. ichdas Zeichen der Zeichenfolge, n, ist die Länge von die Zeichenfolge und ^ zeigt Potenzierung an.

Warum wird 31 als Multiplikator verwendet?

Ich verstehe, dass der Multiplikator eine relativ große Primzahl sein sollte. Warum also nicht 29, 37 oder gar 97?

428
jacobko

Laut Joshua Blochs Effective Java (ein Buch, das nicht genug empfohlen werden kann und das ich dank ständiger Erwähnungen über stackoverflow gekauft habe):

Der Wert 31 wurde gewählt, weil es eine ungerade Primzahl ist. Wenn sie gleichmäßig wäre und die Multiplikation überläuft, gehen Informationen verloren, da die Multiplikation mit 2 der Verschiebung entspricht. Der Vorteil einer Primzahl ist weniger klar, aber traditionell. Eine Nice-Eigenschaft von 31 besteht darin, dass die Multiplikation durch eine Verschiebung und eine Subtraktion für eine bessere Leistung ersetzt werden kann: 31 * i == (i << 5) - i. Moderne VMs führen diese Art der Optimierung automatisch durch.

(aus Kapitel 3, Punkt 9: Hashcode immer überschreiben, wenn Sie Gleiches überschreiben, Seite 48)

364
matt b

Wie Goodrich und Tamassia weisen Sie darauf hin, dass Sie, wenn Sie mehr als 50.000 englische Wörter (gebildet als Vereinigung der Wortlisten in zwei Unix-Varianten) verwenden, die Konstanten 31, 33, 37, 39 und 41 verwenden jeweils weniger als 7 Kollisionen. Wenn man dies weiß, sollte es nicht überraschen, dass viele Java-Implementierungen eine dieser Konstanten wählen. 

Zufälligerweise war ich gerade dabei, den Abschnitt "Polynom-Hash-Codes" zu lesen, als ich diese Frage sah.

BEARBEITEN: Hier ist ein Link zum Buch ~ 10mb PDF, auf das ich mich oben beziehe. Siehe Abschnitt 10.2 Hash-Tabellen (Seite 413) von Datenstrukturen und Algorithmen in Java

75
JohnZaj

Bei (meistens) alten Prozessoren kann die Multiplikation mit 31 relativ günstig sein. In einem ARM ist es beispielsweise nur eine Anweisung:

RSB       r1, r0, r0, ASL #5    ; r1 := - r0 + (r0<<5)

Die meisten anderen Prozessoren benötigen eine separate Verschiebe- und Subtraktionsanweisung. Wenn Ihr Multiplikator jedoch langsam ist, ist dies immer noch ein Gewinn. Moderne Prozessoren haben in der Regel schnelle Multiplikatoren, so dass es keinen großen Unterschied macht, solange 32 auf die richtige Seite geht.

Es ist kein guter Hash-Algorithmus, aber es ist gut genug und besser als der 1.0-Code (und sehr viel besser als die 1.0-Spezifikation!).

55

Durch das Multiplizieren werden Bits nach links verschoben. Dadurch wird mehr Platz für Hash-Codes beansprucht, wodurch Kollisionen reduziert werden.

Wenn keine Zweierpotenz verwendet wird, werden die Bits ganz rechts unten ebenfalls mit Daten gefüllt, die mit den nächsten Daten gemischt werden, die in den Hash eingegeben werden.

Der Ausdruck n * 31 entspricht (n << 5) - n.

28
erickson

Sie können Blochs ursprüngliche Argumentation unter "Kommentare" in http://bugs.Java.com/bugdatabase/view_bug.do?bug_id=4045622 nachlesen. Er untersuchte die Leistung verschiedener Hash-Funktionen in Bezug auf die resultierende "durchschnittliche Kettengröße" in einer Hash-Tabelle. P(31) war während dieser Zeit eine der häufigsten Funktionen, die er in K & Rs Buch fand (aber selbst Kernighan und Ritchie konnten sich nicht erinnern, woher sie kamen). Am Ende musste er sich im Grunde eines aussuchen und so nahm er P(31), da es scheinbar gut genug war. Obwohl P(33) nicht wirklich schlechter war und die Multiplikation mit 33 gleich schnell berechnet werden kann (nur eine Verschiebung um 5 und eine Addition), entschied er sich für 31, da 33 keine Primzahl ist:

Von den übrigen Viertens würde ich wahrscheinlich P (31) auswählen, da dies am billigsten für ein RISC ist Maschine (weil 31 die Differenz zweier Zweierpotenzen ist). P(33) ist ähnlich billig zu berechnen, aber die Leistung ist geringfügig schlechter, und 33 ist zusammengesetzt, was mich etwas nervös macht.

Die Argumentation war also nicht so rational, wie viele Antworten hier scheinen. Aber wir sind alle gut darin, nach Bauchentscheidungen vernünftige Gründe zu finden (und Bloch könnte auch dazu neigen).

24
David Ongaro

Eigentlich würde 37 ziemlich gut funktionieren! z: = 37 * x kann als y := x + 8 * x; z := x + 4 * y berechnet werden. Beide Schritte entsprechen einer x86-Anweisung von LEA, dies ist also extrem schnell. 

Tatsächlich könnte die Multiplikation mit der noch größeren Primzahl 73 mit der gleichen Geschwindigkeit erfolgen, indem y := x + 8 * x; z := x + 8 * y eingestellt wird.

Die Verwendung von 73 oder 37 (anstelle von 31) ist möglicherweise besser, da dies zu dichterem Code führt: Die beiden LEA-Anweisungen benötigen nur 6 Bytes gegenüber den 7 Bytes für move + shift + subtract für die Multiplikation mit 31 Ein möglicher Nachteil ist, dass die hier verwendeten LEA-Anweisungen mit drei Argumenten in der Sandy-Bridge-Architektur von Intel langsamer wurden, mit einer erhöhten Latenz von 3 Zyklen.

73 ist Sheldon Coopers Lieblingsnummer.

22
hrr

Neil Coffey erklärt warum 31 verwendet wird unter Die Vorurteile bereinigen.

Wenn Sie 31 verwenden, erhalten Sie eine gleichmäßigere Wahrscheinlichkeitsverteilung für die Hash-Funktion.

18
TheJuice

Von JDK-4045622 , wo Joshua Bloch die Gründe beschreibt, warum diese bestimmte (neue) String.hashCode()-Implementierung gewählt wurde

Die nachstehende Tabelle fasst die Leistung der verschiedenen Hashs zusammen oben beschriebene Funktionen für drei Datensätze:

1) Alle Wörter und Phrasen mit Einträgen in Merriam-Webster's 2nd Int'l Unabridged Dictionary (311.141 Zeichenfolgen, durchschnittliche Länge 10 Zeichen).

2) Alle Zeichenfolgen in/bin/,/usr/bin/,/usr/lib/,/usr/ucb/ und/usr/openwin/bin/* (66.304 Zeichenfolgen, durchschnittliche Länge 21 Zeichen).

3) Eine Liste von URLs, die von einem Web-Crawler gesammelt wurden, der mehrere .__ ausgeführt hat. Stunden letzte Nacht (28.372 Zeichenfolgen, durchschnittliche Länge 49 Zeichen).

Die in der Tabelle gezeigte Leistungsmetrik ist die "durchschnittliche Kettengröße" über alle Elemente in der Hash-Tabelle (d. h. den erwarteten Wert der der Anzahl der Schlüsselvergleiche zum Nachschlagen eines Elements).

                          Webster's   Code Strings    URLs
                          ---------   ------------    ----
Current Java Fn.          1.2509      1.2738          13.2560
P(37)    [Java]           1.2508      1.2481          1.2454
P(65599) [Aho et al]      1.2490      1.2510          1.2450
P(31)    [K+R]            1.2500      1.2488          1.2425
P(33)    [Torek]          1.2500      1.2500          1.2453
Vo's Fn                   1.2487      1.2471          1.2462
WAIS Fn                   1.2497      1.2519          1.2452
Weinberger's Fn(MatPak)   6.5169      7.2142          30.6864
Weinberger's Fn(24)       1.3222      1.2791          1.9732
Weinberger's Fn(28)       1.2530      1.2506          1.2439

Wenn Sie diese Tabelle betrachten, ist es klar, dass alle Funktionen außer die aktuelle Java-Funktion und die zwei defekten Versionen von Weinbergers Funktion bieten ausgezeichnete, kaum unterscheidbare Leistung. ICH Vermuten Sie stark, dass diese Leistung im Wesentlichen die .__ ist. "theoretisches Ideal", was Sie erhalten würden, wenn Sie eine echte Zufallswahl verwenden. Zahlengenerator anstelle einer Hash-Funktion.

Ich würde die WAIS-Funktion ausschließen, da ihre Spezifikation Seiten mit Zufallszahlen enthält und ihre Leistung nicht besser ist als die von wesentlich einfachere Funktionen. Jede der verbleibenden sechs Funktionen scheint wie ausgezeichnete Auswahl, aber wir müssen eine auswählen. Ich würde wohl ausschließen. Vo's Variante und Weinbergers Funktion wegen ihres Zusatzes Komplexität, wenn auch geringfügig. Von den restlichen vier würde ich wahrscheinlich .__ auswählen. P (31), da dies auf einem RISC-Rechner am billigsten ist (weil 31 .__ die Differenz zweier Zweierpotenzen ist). P(33) ist ähnlich billig zu rechnen, aber die Leistung ist etwas schlechter, und 33 ist Komposit, was mich etwas nervös macht.

Josh

7
Flow

Bloch geht nicht ganz darauf ein, aber ich habe immer gehört/geglaubt, dass dies grundlegende Algebra ist. Hashes reduzieren sich auf Multiplikations- und Moduloperationen, was bedeutet, dass Sie niemals Zahlen mit gemeinsamen Faktoren verwenden möchten, wenn Sie ihnen helfen können. Mit anderen Worten, relative Primzahlen sorgen für eine gleichmäßige Verteilung der Antworten.

Die Nummern, aus denen ein Hash besteht, sind normalerweise:

  • modul des Datentyps, den Sie in eingeben (2 ^ 32 oder 2 ^ 64)
  • modul der Bucket-Zählung in Ihrer Hashtabelle (variiert. In Java wurden Primzahlen verwendet, jetzt 2 ^ n)
  • multiplizieren oder verschieben Sie eine magische Zahl in Ihrer Mischfunktion
  • Der Eingabewert

Man kann wirklich nur ein paar dieser Werte kontrollieren, daher ist etwas mehr Sorgfalt geboten.

4
Jason

Ich bin mir nicht sicher, aber ich würde vermuten, dass sie einige Primzahlen getestet und festgestellt haben, dass 31 die beste Verteilung über einige mögliche Strings liefert.

4
Dave L.

Dies liegt daran, dass 31 eine Nice-Eigenschaft hat - ihre Multiplikation kann durch eine bitweise Verschiebung ersetzt werden, die schneller ist als die Standardmultiplikation:

31 * i == (i << 5) - i
0
yoAlex5

In der neuesten Version von JDK wird noch 31 verwendet. https://docs.Oracle.com/de/Java/javase/11/docs/api/Java.base/Java/lang/String.html#hashCode ()

Der Zweck des Hash-Strings ist

  • unique (Siehe Operator ^ im Hashcode-Berechnungsdokument, es hilft einzigartig)
  • günstige kosten für die berechnung

31 ist der maximale Wert kann in ein 8-Bit-Register (= 1 Byte) eingegeben werden. ist die größte Primzahl im 1-Byte-Register, die ungerade Zahl.

Multipliziere 31 ist << 5, dann subtrahiere sich selbst, brauche daher billige Ressourcen.

0
foobarfuu