590 Stimmen

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

Gemäß der Java-Dokumentation ist die [Hash-Code](http://java.sun.com/javase/6/docs/api/java/lang/String.html#hashCode()) für eine String Objekt wird wie folgt berechnet:

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

mit int Arithmetik, wobei s[i] ist die i Zeichen der Zeichenkette, n ist die Länge von der Zeichenkette, und ^ bedeutet Potenzierung.

Warum wird 31 als Multiplikator verwendet?

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

16 Stimmen

Wenn es 29 oder 37 oder sogar 97 wäre, würden Sie fragen: "Warum nicht 31?

3 Stimmen

@EJP es ist wichtig, den Grund für die Wahl einer Nummer zu kennen, es sei denn, die Nummer ist das Ergebnis eines schwarzen Zaubertricks.

0 Stimmen

Es gibt einen Blogbeitrag von @peter-lawrey darüber hier: vanilla-java.github.io/2018/08/12/ und hier: vanilla-java.github.io/2018/08/15/

487voto

matt b Punkte 135206

Nach dem Buch von Joshua Bloch Leistungsfähiges Java (ein Buch, das nicht genug empfohlen werden kann und das ich dank der ständigen Erwähnungen auf Stackoverflow gekauft habe):

Der Wert 31 wurde gewählt, weil er eine ungerade Primzahl ist. Wäre er gerade und die Multiplikation würde überlaufen, würden Informationen verloren gehen, da die Multiplikation mit 2 einer Verschiebung gleichkommt. Der Vorteil der Verwendung einer Primzahl ist weniger klar, aber er ist traditionell. Eine schöne Eigenschaft von 31 ist, dass die Multiplikation durch eine Verschiebung und eine Subtraktion ersetzt werden kann, um die Leistung zu verbessern: 31 * i == (i << 5) - i . Moderne VMs führen diese Art der Optimierung automatisch durch.

(aus Kapitel 3, Punkt 9: Überschreiben Sie immer den Hashcode, wenn Sie die Gleichung überschreiben, Seite 48)

415 Stimmen

Nun, alle Primzahlen sind ungerade, außer 2. Ich sag's ja nur.

49 Stimmen

Ich glaube nicht, dass Bloch damit sagen will, dass sie gewählt wurde, weil sie eine ungerade Primzahl ist, sondern weil sie ungerade UND eine Primzahl ist (UND weil sie leicht zu einer Verschiebung/Subtraktion optimiert werden kann).

60 Stimmen

Die 31 wurde gewählt, weil sie eine ungerade Primzahl ist??? Das ergibt keinen Sinn - ich sage, dass 31 gewählt wurde, weil sie die beste Verteilung ergab - check computinglife.wordpress.com/2008/11/20/

104voto

JohnZaj Punkte 2970

Goodrich und Tamassia berechneten anhand von über 50.000 englischen Wörtern (gebildet aus der Vereinigung der Wortlisten zweier Unix-Varianten), dass die Verwendung der Konstanten 31, 33, 37, 39 und 41 jeweils zu weniger als 7 Kollisionen führt. Dies mag der Grund dafür sein, dass so viele Java-Implementierungen solche Konstanten wählen.

Siehe Abschnitt 9.2 Hash-Tabellen (Seite 522) von Datenstrukturen und Algorithmen in Java .

9 Stimmen

Beachten Sie jedoch, dass Sie VIEL mehr Kollisionen bekommen können, wenn Sie irgendeinen internationalen Zeichensatz mit gängigen Zeichen außerhalb des ASCII-Bereichs verwenden. Zumindest habe ich dies für 31 und Deutsch überprüft. Ich denke also, dass die Wahl von 31 nicht richtig ist.

62voto

Tom Hawtin - tackline Punkte 142461

Auf (meist) alten Prozessoren kann die Multiplikation mit 31 relativ billig sein. Auf einem ARM ist es zum Beispiel nur eine Anweisung:

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

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

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

7 Stimmen

Komischerweise ist die Multiplikation mit 31 auf meinem Desktop-Rechner tatsächlich etwas langsamer als die Multiplikation mit, sagen wir, 92821. Ich vermute, dass der Compiler versucht, die Multiplikation auch auf Shift und Addition zu "optimieren" :-)

1 Stimmen

Ich glaube nicht, dass ich jemals einen ARM verwendet habe, der nicht bei allen Werten im Bereich von +/-255 gleich schnell war. Die Verwendung einer 2er-Potenz minus eins hat den unglücklichen Effekt, dass eine übereinstimmende Änderung von zwei Werten den Hash-Code um eine Zweierpotenz ändert. Ein Wert von -31 wäre besser gewesen, und ich würde denken, dass etwas wie -83 (64+16+2+1) noch besser gewesen wäre (blenderize bits etwas besser).

0 Stimmen

@supercat Nicht überzeugt vom Minus. Scheint so, als würde man wieder auf Nullen zusteuern. / String.hashCode vor dem StrongARM, der, wenn ich mich recht erinnere, einen 8-Bit-Multiplikator einführte und möglicherweise auf zwei Zyklen für die kombinierten arithmetischen/logischen und Schiebeoperationen erhöht wurde.

45voto

erickson Punkte 256579

Beim Multiplizieren werden die Bits nach links verschoben. Dadurch wird der verfügbare Platz der Hash-Codes besser genutzt und Kollisionen werden reduziert.

Da keine Zweierpotenz verwendet wird, werden auch die untergeordneten, ganz rechts liegenden Bits aufgefüllt und mit den nächsten Daten gemischt, die in den Hash eingehen.

Der Ausdruck n * 31 ist gleichbedeutend mit (n << 5) - n .

40voto

David Ongaro Punkte 3037

Die ursprüngliche Argumentation von Bloch können Sie unter "Kommentare" in http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622 . Er untersuchte die Leistung verschiedener Hash-Funktionen im Hinblick auf die resultierende "durchschnittliche Kettengröße" in einer Hash-Tabelle. P(31) war eine der damals üblichen Funktionen, die er in dem Buch von K&R gefunden hatte (aber selbst Kernighan und Ritchie konnten sich nicht erinnern, woher sie stammte). Am Ende musste er sich im Grunde für eine entscheiden, und so nahm er P(31) da sie anscheinend gut genug funktioniert. Auch wenn P(33) nicht wirklich schlechter war und die Multiplikation mit 33 ebenso schnell zu berechnen ist (nur eine Verschiebung um 5 und eine Addition), entschied er sich für 31, da 33 keine Primzahl ist:

Von den übrigen würde ich wahrscheinlich P(31) wählen, da es auf einem RISC-Rechner am einfachsten zu berechnen ist (weil 31 die Differenz zweier Zweierpotenzen ist). Maschine zu berechnen ist (weil 31 die Differenz zweier Zweierpotenzen ist). P(33) ist ähnlich billig zu berechnen, aber seine Leistung ist geringfügig schlechter, und 33 ist zusammengesetzt, was mich ein wenig nervös macht.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X