Was ist eine sinnvolle Primzahl für die Hashcode-Berechnung?

Lesezeit: 2 Minuten

Benutzer-Avatar
Hans-Peter Störr

Eclipse 3.5 hat ein sehr nettes Feature, um Java hashCode()-Funktionen zu generieren. Es würde zum Beispiel (leicht gekürzt:)

class HashTest {
    int i;
    int j;        
    public int hashCode() {
        final int prime = 31;
        int result = prime + i;
        result = prime * result + j;
        return result;
    }
}

(Wenn Sie mehr Attribute in der Klasse haben, result = prime * result + attribute.hashCode(); wird für jedes weitere Attribut wiederholt. Bei ints kann .hashCode() weggelassen werden.)

Dies scheint in Ordnung zu sein, aber für die Wahl 31 für die Primzahl. Es stammt wahrscheinlich aus der hashCode-Implementierung von Java String, die aus Performance-Gründen verwendet wurde, die nach der Einführung von Hardware-Multiplikatoren längst vorbei sind. Hier haben Sie viele Hashcode-Kollisionen für kleine Werte von i und j: zum Beispiel (0,0) und (-1,31) haben denselben Wert. Ich denke, das ist eine schlechte Sache (TM), da häufig kleine Werte auftreten. Für String.hashCode finden Sie auch viele kurze Zeichenfolgen mit demselben Hashcode, zum Beispiel “Ca” und “DB”. Wenn Sie eine große Primzahl nehmen, verschwindet dieses Problem, wenn Sie die Primzahl rechts wählen.

Also meine Frage: Was ist eine gute Primzahl zu wählen? Nach welchen Kriterien finden Sie es?

Dies ist als allgemeine Frage gedacht – daher möchte ich keinen Bereich für i und j angeben. Aber ich nehme an, dass in den meisten Anwendungen relativ kleine Werte häufiger vorkommen als große Werte. (Wenn Sie große Werte haben, ist die Wahl der Primzahl wahrscheinlich unwichtig.) Es macht vielleicht keinen großen Unterschied, aber eine bessere Wahl ist eine einfache und offensichtliche Möglichkeit, dies zu verbessern – warum also nicht? Commons lang HashCodeBuilder schlägt auch merkwürdig kleine Werte vor.

(Klärung: das ist nicht ein Duplikat von Warum verwendet Javas hashCode() in String 31 als Multiplikator? da sich meine Frage nicht auf die Geschichte der 31 im JDK bezieht, sondern darauf, was ein besserer Wert in neuem Code wäre, der dieselbe grundlegende Vorlage verwendet. Keine der Antworten dort versuchen, das zu beantworten.)

  • 31 ist immer noch gut, da nicht unbedingt eine Konstante geladen werden muss. Auf einem ARM-Prozessor (mindestens einer, der von etwa 99,9997 % der Mobiltelefone verwendet wird) *31 kann in einer einzigen Anweisung durchgeführt werden. In Wirklichkeit ist jede ungerade Zahl, egal ob Primzahl oder nicht, gut genug.

    – Tom Hawtin – Angelleine

    2. Dezember 2009 um 21:39 Uhr

  • Ich dachte an Desktop-Programme, bei denen es egal ist, ob man 31 oder 1327144003 wählt. Seltsamerweise ist das Multiplizieren mit 31 auf meinem Rechner tatsächlich etwas langsamer – wahrscheinlich ist eine Optimierung schief gelaufen. 😎

    – Hans-Peter Störr

    3. Dezember 2009 um 6:21 Uhr

  • Primzahlen der Form p = (2^n-1) eignen sich zur Optimierung von x * p = (p << n) - p was der Compiler normalerweise tut. Von Joshua Bloch, Effektives Java, Kapitel 3, Punkt 9. SO Frage stackoverflow.com/questions/299304/…

    – corsiKa

    16. Februar 2011 um 19:30 Uhr

  • und mit Integer <128 multiplizieren haben zusätzlichen Schub in jvm.. 2^n-1prime, smallish .. das ergibt 31.

    – J-16 SDiZ

    27. November 2014 um 10:58 Uhr

  • @MarkRotteveel Bitte beachten Sie, dass dies ganz anders ist als [Why does Java’s hashCode() in String use 31 as a multiplier?][1] da es hier nicht um die Geschichte von 31 geht, sondern darum, was eine bessere Wahl wäre, anstatt 31 zu verwenden, ohne zusätzliche Bibliotheken oder völlig andere Methoden zur Berechnung von Hashes zu verwenden. Keine der Antworten dort spricht das an. [1]: stackoverflow.com/questions/299304/…

    – Hans-Peter Störr

    5. September 2017 um 10:39 Uhr


Benutzer-Avatar
Hans-Peter Störr

Ich empfehle die Verwendung 92821. Hier ist der Grund.

Um darauf eine sinnvolle Antwort zu geben, muss man etwas über die möglichen Werte von wissen i und j. Das einzige, was mir allgemein einfällt, ist, dass in vielen Fällen kleine Werte häufiger vorkommen als große Werte. (Die Wahrscheinlichkeit, dass 15 als Wert in Ihrem Programm erscheint, ist viel besser als beispielsweise 438281923.) Daher scheint es eine gute Idee zu sein, die kleinste Hashcode-Kollision so groß wie möglich zu machen, indem Sie eine geeignete Primzahl wählen. Für 31 eher schlecht – schon für i=-1 und j=31 Sie haben den gleichen Hashwert wie für i=0 und j=0.

Da dies interessant ist, habe ich ein kleines Programm geschrieben, das den gesamten int-Bereich nach der besten Primzahl in diesem Sinne durchsucht. Das heißt, für jede Primzahl suchte ich nach dem Mindestwert von Math.abs(i) + Math.abs(j) über alle Werte von i,j die den gleichen Hashcode haben wie 0,0und dann die Primzahl genommen, wo dieser minimale Wert so groß wie möglich ist.

Trommelwirbel: Die beste Primzahl in diesem Sinne ist 486187739 (wobei die kleinste Kollision ist i=-25486, j=67194). Fast genauso gut und viel leichter zu merken ist 92821 mit der kleinsten Kollision i=-46272 and j=46016.

Wenn Sie “klein” eine andere Bedeutung geben und das Minimum sein wollen Math.sqrt(i*i+j*j) für die möglichst große kollision sind die ergebnisse etwas anders: am besten wäre 1322837333 mit i=-6815 and j=70091aber mein Favorit 92821 (kleinste Kollision -46272,46016) ist wieder fast so gut wie der Bestwert.

Ich gebe zu, dass es ziemlich umstritten ist, ob diese Berechnungen in der Praxis viel Sinn machen. Aber ich denke, dass es viel sinnvoller ist, 92821 als Primzahl zu nehmen als 31, es sei denn, Sie haben gute Gründe, dies nicht zu tun.

  • Sie suchen nach einer magischen Zahl für einen perfekten Hash, oder zumindest einen fast perfekten. Ich wäre mehr daran interessiert, eine Lösung für beliebige Eingaben bis zur Hash-Größe (z. B. 4 2-Byte-Werte in einem 8-Byte-Hashcode) zu sehen, als diesen speziellen Fall einer einfachen Transposition.

    – Jason

    12. Mai 2010 um 21:20 Uhr


  • 8-Byte-Hashcode? Zumindest in Java sind dies 4 Bytes. Wie auch immer: Sie könnten einfach das Schema fortsetzen, das bei der Eclipse-HashCode-Generierung verwendet wird: result = prime * result + i; Ergebnis = Primzahl * Ergebnis + j; und so weiter. Dafür ist 92821 als Prime wohl eine gute Wahl – zumindest deutlich besser als die eclipse default 31.

    – Hans-Peter Störr

    18. Mai 2010 um 8:53 Uhr

  • Es ist nicht nur falsch, eine kleine Konstante zu verwenden, es ist auch falsch, sie wiederzuverwenden, da Sie Kollisionen wie bekommen newArrayList("a", "bc").hashCode() == newArrayList("ab", "c").hashCode() (Mein Beispiel funktioniert möglicherweise nicht, aber etwas Ähnliches funktioniert).

    – maaartinus

    4. Juni 2017 um 3:19 Uhr

  • @maaartinus Sie haben Recht, dass es viele viel bessere Hash-Algorithmen gibt. Ich habe nur versucht, eine einfache, aber lohnende Verbesserung eines oft verwendeten einfachen Algorithmus aufzuzeigen. Wenn Sie wirklich gute Eigenschaften wollen, gibt es dafür Bibliotheken, die viel besser sind, aber das ist oft übertrieben.

    – Hans-Peter Störr

    6. Juni 2017 um 5:57 Uhr

  • @ToolmakerSteve Ich bezweifle auch, dass 10% machbar sind. Für eine Anwendung lohnt sich der Aufwand kaum. Wenn wir das gesamte Java-Hashing neu gestalten könnten, könnten 10 % erreichbar sein (um dumme Kollisionen wie die zu vermeiden hashCode Null für jeden neuen Map.Entry mit gleichem Schlüssel und Wert usw.), während sogar 0,1 % wahrscheinlich eine würdige Verbesserung darstellen.

    – maaartinus

    1. März 2018 um 21:49 Uhr

Eigentlich, wenn Sie eine Primzahl so groß nehmen, dass sie nahe kommt INT_MAX, haben Sie das gleiche Problem wegen der Modulo-Arithmetik. Wenn Sie erwarten, hauptsächlich Zeichenfolgen der Länge 2 zu hashen, vielleicht eine Primzahl in der Nähe der Quadratwurzel von INT_MAX wäre am besten, wenn die Zeichenfolgen, die Sie hashen, länger sind, spielt es keine Rolle, und Kollisionen sind sowieso unvermeidlich …

  • Richtig, die Modulo-Arithmetik macht das Problem schwierig und interessant. Ich denke, ich werde ein kleines Programm schreiben, um nach einer guten Lösung zu suchen. 🙂

    – Hans-Peter Störr

    3. Dezember 2009 um 6:49 Uhr

Kollisionen sind vielleicht kein so großes Problem … Das Hauptziel des Hashs ist es, die Verwendung von Gleichheitszeichen für 1: 1-Vergleiche zu vermeiden. Wenn Sie eine Implementierung haben, bei der equals “im Allgemeinen” extrem billig für Objekte ist, die kollidierte Hashes haben, dann ist dies (überhaupt) kein Problem.

Letztendlich hängt die beste Art des Hashings davon ab, was Sie vergleichen. Im Fall eines int-Paares (wie in Ihrem Beispiel) könnte die Verwendung einfacher bitweiser Operatoren ausreichen (wie die Verwendung von & oder ^).

  • Natürlich macht es nicht viel aus, aber das Ändern der Primzahl ist eine offensichtliche und einfache Möglichkeit, die Dinge zu verbessern. Warum also nicht?

    – Hans-Peter Störr

    3. Dezember 2009 um 6:17 Uhr

  • Einverstanden. Ich wollte in erster Linie ein wenig betonen, dass die Verwendung von Primzahlen nicht der Fall ist nur Vorgehensweise, da die Frage letztendlich einen sehr “allgemeinen” Umfang hat.

    – Romain

    3. Dezember 2009 um 8:14 Uhr

  • Übrigens: Die Verwendung von && wäre sehr schlecht, da dies dazu neigt, die Anzahl der nach jedem Schritt gesetzten Bits zu verringern. Die Verwendung von ^ ist besser, aber wie jemand darauf hingewiesen hat, würde die Verwendung von i ^ j bedeuten, dass das Ergebnis 0 ist, wenn sie gleich sind, was intuitiv auch ein ziemlich häufiger Fall ist.

    – Hans-Peter Störr

    26. August 2020 um 10:53 Uhr

Sie müssen Ihren Bereich für i und j definieren. Sie könnten für beide eine Primzahl verwenden.

public int hashCode() {
   http://primes.utm.edu/curios/ ;)
   return 97654321 * i ^ 12356789 * j;
}

Ich würde 7243 wählen. Groß genug, um Kollisionen mit kleinen Zahlen zu vermeiden. Läuft nicht schnell zu kleinen Zahlen über.

  • Ich benutze die ersten 1000 Primzahlen als praktische Quelle für kleine Primzahlen primes.utm.edu/lists/small/1000.txt

    – Steve Kuo

    3. Dezember 2009 um 1:51 Uhr

  • Ich denke nicht, dass Überlauf wichtig ist – wenn die Primzahl groß genug ist, wird das Ergebnis auch nach dem Überlauf groß sein. Ich dachte an so etwas wie 1327144003.

    – Hans-Peter Störr

    3. Dezember 2009 um 6:16 Uhr

Benutzer-Avatar
neoedmund

Ich möchte nur darauf hinweisen, dass Hashcode nichts mit Prime zu tun hat. In der JDK-Implementierung

for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }

Ich fand, wenn Sie ersetzen 31 mit 27das Ergebnis ist sehr ähnlich.

  • Ich benutze die ersten 1000 Primzahlen als praktische Quelle für kleine Primzahlen primes.utm.edu/lists/small/1000.txt

    – Steve Kuo

    3. Dezember 2009 um 1:51 Uhr

  • Ich denke nicht, dass Überlauf wichtig ist – wenn die Primzahl groß genug ist, wird das Ergebnis auch nach dem Überlauf groß sein. Ich dachte an so etwas wie 1327144003.

    – Hans-Peter Störr

    3. Dezember 2009 um 6:16 Uhr

1143390cookie-checkWas ist eine sinnvolle Primzahl für die Hashcode-Berechnung?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy