web-dev-qa-db-fra.com

Pourquoi hashCode () de Java dans String utilise-t-il 31 comme multiplicateur?

Selon la documentation Java, le code hash pour un objet String est calculé comme suit:

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

en utilisant l'arithmétique int, où s[i] est le jee caractère de la chaîne, n est la longueur de la chaîne et ^ indique une exponentiation.

Pourquoi 31 est-il utilisé comme multiplicateur?

Je comprends que le multiplicateur devrait être un nombre premier relativement grand. Alors pourquoi pas 29, ou 37, voire 97?

428
jacobko

Selon Joshua Bloch's Effective Java (un livre que je ne saurais trop recommander, et que j'ai acheté grâce aux mentions continues sur stackoverflow):

La valeur 31 a été choisie parce que c'est un nombre premier impair. S'il était égal et que la multiplication débordait, l'information serait perdue, car la multiplication par 2 équivaut à un décalage. L'avantage d'utiliser un prime est moins évident, mais c'est traditionnel. Une propriété de Nice de 31 est que la multiplication peut être remplacée par un décalage et une soustraction pour de meilleures performances: 31 * i == (i << 5) - i. Les machines virtuelles modernes effectuent ce type d'optimisation automatiquement.

(du chapitre 3, élément 9: substituez toujours le hashcode lorsque vous écrasez égal à, page 48)

364
matt b

Comme Goodrich et Tamassia précisez, si vous prenez plus de 50 000 mots anglais (formés comme l'union des listes de mots fournies dans deux variantes d'Unix), en utilisant les constantes 31, 33, 37, 41 et 41, moins de 7 collisions dans chaque cas. Sachant cela, il n’est pas surprenant que de nombreuses implémentations Java choisissent l’une de ces constantes. 

Par coïncidence, j'étais en train de lire la section "codes de hachage polynomiaux" quand j'ai vu cette question.

EDIT: voici un lien vers le livre ~ 10mb PDF dont je parle ci-dessus. Voir la section 10.2 Tables de hachage (page 413) de Structures de données et algorithmes en Java

75
JohnZaj

Sur (principalement) les vieux processeurs, multiplier par 31 peut être relativement peu coûteux. Sur un bras, par exemple, ce n’est qu’une instruction:

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

La plupart des autres processeurs nécessiteraient une instruction de soustraction et de décalage séparée. Cependant, si votre multiplicateur est lent, c'est toujours une victoire. Les processeurs modernes ont tendance à avoir des multiplicateurs rapides, de sorte que cela ne fait pas beaucoup de différence, du moment que 32 est du bon côté.

Ce n'est pas un excellent algorithme de hachage, mais il est assez bon et meilleur que le code 1.0 (et bien meilleur que la spécification 1.0!).

55

En se multipliant, les bits sont décalés vers la gauche. Ceci utilise plus de l'espace disponible des codes de hachage, réduisant ainsi les collisions.

En n'utilisant pas une puissance de deux, les bits de l'ordre inférieur et le plus à droite sont également renseignés pour être mélangés au prochain élément de données entrant dans le hachage.

L'expression n * 31 est équivalente à (n << 5) - n.

28
erickson

Vous pouvez lire le raisonnement original de Bloch sous "Commentaires" dans http://bugs.Java.com/bugdatabase/view_bug.do?bug_id=4045622 . Il a étudié la performance de différentes fonctions de hachage en ce qui concerne la "taille de chaîne moyenne" résultante dans une table de hachage. P(31) était l'une des fonctions courantes de ce temps-là qu'il avait trouvée dans le livre de K & R (mais même Kernighan et Ritchie ne pouvaient pas se rappeler d'où cela venait). En fin de compte, il a dû en choisir un et il a donc pris P(31) car il semblait fonctionner assez bien. Même si P(33) n'était pas vraiment pire et que la multiplication par 33 est également rapide à calculer (juste un décalage de 5 et un ajout), il a opté pour 31, car 33 n'est pas un nombre premier:

Du reste Quatrièmement, je choisirais probablement P (31), car c’est le moins cher à calculer avec un RISC machine (car 31 est la différence de deux puissances de deux). P(33) est de même pas cher à calculer, mais sa performance est légèrement pire, et 33 est composite, ce qui me rend un peu nerveux.

Le raisonnement n'était donc pas aussi rationnel que le suggèrent beaucoup de réponses. Mais nous sommes tous bons pour trouver des raisons rationnelles après les décisions intestinales (et même Bloch pourrait être enclin à cela).

24
David Ongaro

En fait, 37 fonctionnerait plutôt bien! z: = 37 * x peut être calculé en tant que y := x + 8 * x; z := x + 4 * y. Les deux étapes correspondent à une instruction LEA x86, ce qui est extrêmement rapide. 

En fait, la multiplication avec le nombre premier plus grand 73 pourrait être effectuée à la même vitesse en définissant y := x + 8 * x; z := x + 8 * y.

Utiliser 73 ou 37 (au lieu de 31) pourrait être préférable, car cela conduit à code plus dense: les deux instructions LEA ne prennent que 6 octets contre 7 octets pour déplacer + déplacer + soustraire pour la multiplication par 31 Un inconvénient possible est que les instructions LEA à 3 arguments utilisées ici sont devenues plus lentes sur l’architecture Intel Bridge Sandy, avec une latence accrue de 3 cycles.

De plus, 73 est le numéro préféré de Sheldon Cooper.

22
hrr

Neil Coffey explique pourquoi 31 est utilisé sous Repasser le biais.

Fondamentalement, l’utilisation de 31 vous donne une distribution des probabilités plus égale pour la fonction de hachage.

18
TheJuice

Extrait de JDK-4045622 , où Joshua Bloch décrit les raisons pour lesquelles cette (nouvelle) implémentation String.hashCode() a été choisie

Le tableau ci-dessous résume les performances des différents hash fonctions décrites ci-dessus, pour trois ensembles de données:

1) Tous les mots et expressions comportant des entrées dans Merriam-Webster's Deuxième dictionnaire intabriqué international (311 141 chaînes, longueur moyenne 10 caractères).

2) Toutes les chaînes de/bin/,/usr/bin/,/usr/lib/,/usr/ucb/ et/usr/openwin/bin/* (66 304 chaînes, longueur moyenne 21 caractères).

3) Une liste des URL rassemblées par un robot d'indexation ayant fonctionné pendant plusieurs années heures la nuit dernière (28 372 chaînes, longueur moyenne 49 caractères).

La mesure de performance indiquée dans le tableau est la "taille moyenne de la chaîne" sur tous les éléments de la table de hachage (c’est-à-dire que la valeur attendue du nombre de clés est comparée pour rechercher un élément).

                          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

En regardant ce tableau, il est clair que toutes les fonctions sauf pour la fonction Java actuelle et les deux versions brisées de .in de Weinberger. fonction offre d'excellentes performances presque impossibles à distinguer. JE présume fortement que cette performance est essentiellement le "idéal théorique", ce que vous obtiendriez si vous utilisiez un vrai aléatoire générateur de nombres à la place d'une fonction de hachage.

J'exclurais la fonction WAIS car sa spécification contient des pages de nombres aléatoires et ses performances ne sont pas meilleures que celles de fonctions beaucoup plus simples. Chacune des six fonctions restantes semble ressembler à excellent choix, mais nous devons en choisir un. Je suppose que j'écarterais La variante de Vo et la fonction de Weinberger en raison de leur ajout complexité, bien que mineure. Parmi les quatre autres, je choisirais probablement P (31), car c’est le moins coûteux à calculer sur une machine RISC (car 31 Est la différence de deux puissances sur deux). P(33) est également bon marché pour calculer, mais sa performance est légèrement pire, et 33 est composite, ce qui me rend un peu nerveux.

Josh

7
Flow

Bloch n'entre pas dans les détails, mais ce que j'ai toujours cru/entendu, c'est qu'il s'agit d'une algèbre fondamentale. Les hachages se résument à des opérations de multiplication et de module, ce qui signifie que vous ne voulez jamais utiliser de nombres avec des facteurs communs si vous pouvez vous aider. En d'autres termes, les nombres premiers premiers fournissent une distribution égale des réponses.

Les chiffres qui composent un hachage sont généralement:

  • module du type de données dans lequel vous l'avez mis (2 ^ 32 ou 2 ^ 64)
  • module du nombre de seaux dans votre table de hachage (varie. En Java, il était auparavant primordial, maintenant 2 ^ n)
  • multiplier ou décaler par un nombre magique dans votre fonction de mixage
  • La valeur d'entrée

Vous ne pouvez vraiment contrôler que quelques-unes de ces valeurs, donc un peu plus d'attention est nécessaire.

4
Jason

Je ne suis pas sûr, mais je suppose qu'ils ont testé un échantillon de nombres premiers et ont découvert que 31 donnaient la meilleure distribution sur un échantillon de chaînes possibles.

4
Dave L.

En effet, 31 a une propriété de Nice - sa multiplication peut être remplacée par un décalage au niveau du bit qui est plus rapide que la multiplication standard:

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

Dans la dernière version de JDK, la version 31 est toujours utilisée. https://docs.Oracle.com/fr/Java/javase/11/docs/api/Java.base/Java/lang/String.html#hashCode ()

Le but de la chaîne de hachage est

  • unique (laisse l'opérateur ^ dans le document de calcul du hashcode, aide unique)
  • coût pas cher pour le calcul

31 is max value peut être inséré dans un registre de 8 bits (= 1 octet). est le nombre premier le plus grand que vous pouvez mettre dans un registre à 1 octet, est un nombre impair.

Multiplier 31 est << 5 puis se soustraire, donc besoin de ressources bon marché.

0
foobarfuu