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

481

Selon la documentation Java, le code de hachage d'un Stringobjet est calculé comme suit:

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

en utilisant l' intarithmétique, où s[i]est le i ème caractère de la chaîne, nest la longueur de la chaîne, et ^indique l'exponentiation.

Pourquoi 31 est-il utilisé comme multiplicateur?

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

jacobko
la source
1
Comparez également stackoverflow.com/questions/1835976/… - Je pense que 31 est un mauvais choix si vous écrivez vos propres fonctions hashCode.
Hans-Peter Störr
6
Si c'était 29, 37, ou même 97, vous vous demanderiez "pourquoi pas 31?"
Marquis de Lorne
2
@EJP, il est important de connaître la raison du choix d'un non. sauf si le nombre est le résultat d'un tour de magie noire.
Dushyant Sabharwal
Il y a un article de blog par @ peter-lawrey à ce sujet ici: vanilla-java.github.io/2018/08/12/… et ici: vanilla-java.github.io/2018/08/15/…
Christophe Roussy
@DushyantSabharwal Mon point est qu'il aurait pu être 29 ou 37 ou 97, ou 41, ou bien d'autres valeurs, sans faire beaucoup de différence pratique. Nous en utilisions 37 en 1976.
Marquis de Lorne

Réponses:

406

Selon Effective Java de Joshua Bloch (un livre qui ne peut pas être assez recommandé, et que j'ai acheté grâce aux mentions continuelles sur stackoverflow):

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

(extrait du chapitre 3, élément 9: toujours remplacer le code de hachage lorsque vous remplacez égal, page 48)

mat b
la source
346
Eh bien, tous les nombres premiers sont impairs, sauf 2. Disons simplement.
Kip
38
Je ne pense pas que Bloch dise qu'il a été choisi parce que c'était un nombre premier impair, mais parce qu'il était étrange ET parce qu'il était premier (ET parce qu'il peut facilement être optimisé en décalage / soustraction).
mat
50
31 a été choisi parce qu'il est un nombre impair impair ??? Cela n'a aucun sens - je dis que 31 a été choisi parce qu'il a donné la meilleure distribution - consultez computinglife.wordpress.com/2008/11/20/…
computinglife
65
Je pense que le choix de 31 est plutôt malheureux. Bien sûr, cela pourrait économiser quelques cycles de processeur sur les anciennes machines, mais vous avez déjà des collisions de hachage sur de courtes chaînes ascii comme "@ et #!, Ou Ca et DB. Cela ne se produit pas si vous choisissez, par exemple, 1327144003, ou à au moins 524287 qui permet également le décalage de bits: 524287 * i == i << 19 - i.
Hans-Peter Störr
15
@Jason Voir ma réponse stackoverflow.com/questions/1835976/… . Mon point est le suivant: vous obtenez beaucoup moins de collisions si vous utilisez un premier plus grand, et vous ne perdez rien de nos jours. Le problème est pire si vous utilisez des langues non anglaises avec des caractères non ascii courants. Et 31 a servi de mauvais exemple pour de nombreux programmeurs lors de l'écriture de leurs propres fonctions hashCode.
Hans-Peter Störr
80

Comme le soulignent Goodrich et Tamassia , si vous prenez plus de 50 000 mots anglais (formés comme l'union des listes de mots fournies dans deux variantes d'Unix), l'utilisation des constantes 31, 33, 37, 39 et 41 produira 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 le lien vers le livre PDF ~ 10 Mo auquel je fais référence ci-dessus. See section 10.2 Hash Tables (page 413) de Data Structures and Algorithms in Java

JohnZaj
la source
6
Notez cependant que vous pourriez obtenir BEAUCOUP plus de collisions si vous utilisez n'importe quel type de jeu de caractères international avec des caractères communs en dehors de la plage ASCII. Au moins, j'ai vérifié cela pour 31 et l'allemand. Je pense donc que le choix de 31 est cassé.
Hans-Peter Störr
1
@jJack, le lien fourni dans votre réponse est rompu.
SK Venkat
Les deux liens de cette réponse sont rompus. De plus, l'argument du premier paragraphe est en quelque sorte incomplet; Comment les autres nombres impairs se comparent-ils aux cinq que vous indiquez sur cette référence?
Mark Amery
58

Sur (principalement) d'anciens processeurs, la multiplication par 31 peut être relativement bon marché. Sur un ARM, 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 décalage et de soustraction distincte. Cependant, si votre multiplicateur est lent, c'est toujours une victoire. Les processeurs modernes ont tendance à avoir des multiplicateurs rapides, donc cela ne fait pas beaucoup de différence, tant que 32 va du bon côté.

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

Tom Hawtin - sellerie
la source
7
Assez drôle, la multiplication avec 31 est sur ma machine de bureau en fait un peu plus lente que la multiplication avec, par exemple, 92821. Je suppose que le compilateur essaie de "l'optimiser" en décalage et en ajoutant également. :-)
Hans-Peter Störr
1
Je ne pense pas avoir déjà utilisé un ARM qui n'était pas aussi rapide avec toutes les valeurs dans la plage +/- 255. L'utilisation d'une puissance de 2 moins un a le malheur qu'un changement correspondant à deux valeurs modifie le code de hachage par une puissance de deux. Une valeur de -31 aurait été meilleure, et je pense que quelque chose comme -83 (64 + 16 + 2 + 1) aurait pu être mieux encore (blenderize bits un peu mieux).
supercat
@supercat Pas convaincu par le moins. Il semble que vous retourniez vers des zéros. / String.hashCodeest antérieur au StrongARM qui, IIRC, a introduit un multiplicateur à 8 bits et peut-être augmenté à deux cycles pour l'arithmétique / logique combinée avec des opérations de décalage.
Tom Hawtin - tackline
1
@ TomHawtin-tackline: En utilisant 31, le hachage de quatre valeurs serait 29791 * a + 961 * b + 31 * c + d; en utilisant -31, ce serait -29791 * a + 961 * b - 31 * c + d. Je ne pense pas que la différence serait significative si les quatre éléments sont indépendants, mais si des paires d'éléments adjacents correspondent, le code de hachage résultant sera la contribution de tous les éléments non appariés, plus un multiple de 32 (parmi ceux appariés). Pour les chaînes, cela n'a peut-être pas trop d'importance, mais si l'on écrit une méthode à usage général pour hacher des agrégations, la situation où les éléments adjacents correspondent sera disproportionnellement courante.
supercat
3
@supercat fun fact, le code de hachage de Map.Entrya été corrigé par spécification pour être key.hashCode() ^ value.hashCode()malgré qu'il ne soit même pas une paire non ordonnée, keyet valuea une signification entièrement différente. Oui, cela implique que Map.of(42, 42).hashCode()ou Map.of("foo", "foo", "bar", "bar").hashCode(), etc., sont vraisemblablement nuls. Alors n'utilisez pas les cartes comme clés pour d'autres cartes…
Holger
33

En multipliant, les bits sont décalés vers la gauche. Cela utilise davantage d'espace disponible de codes de hachage, ce qui réduit les collisions.

En n'utilisant pas une puissance de deux, les bits de poids faible de droite sont également remplis, pour être mélangés avec la prochaine donnée entrant dans le hachage.

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

erickson
la source
29

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é les performances 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 communes à cette époque qu'il a trouvées 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 l'a donc pris P(31)car il semblait fonctionner assez bien. Même si ce 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:

Parmi les quatre autres, je choisirais probablement P (31), car c'est le moins cher à calculer sur une machine RISC (car 31 est la différence de deux puissances de deux). P (33) est également bon marché à calculer, mais ses performances sont légèrement inférieures et 33 est composite, ce qui me rend un peu nerveux.

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

David Ongaro
la source
2
Une recherche approfondie et une réponse impartiale!
Vishal K
22

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

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

Utiliser 73 ou 37 (au lieu de 31) pourrait être mieux, car cela conduit à un code plus dense : les deux instructions LEA ne prennent que 6 octets contre les 7 octets pour déplacer + déplacer + soustraire pour la multiplication par 31. Une mise en garde possible est que les instructions LEA à 3 arguments utilisées ici sont devenues plus lentes sur l'architecture du pont Sandy d'Intel, avec une latence accrue de 3 cycles.

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

hrr
la source
5
Êtes-vous un programmeur pascal ou quelque chose? qu'est-ce que c'est: = stuff?
Mainguy
11
@Mainguy Il s'agit en fait de la syntaxe ALGOL et est utilisé assez souvent en pseudo-code.
ApproachingDarknessFish
4
mais dans l'assemblage ARM, la multiplication par 31 peut être effectuée en une seule instruction
phuclv
Dans TPOP (1999), on peut lire sur Java (p.57): "... Le problème a été résolu en remplaçant le hachage par un équivalent à celui que nous avons montré (avec un multiplicateur de 37 ) ..."
miku
19

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

Fondamentalement, l'utilisation de 31 vous donne une distribution de probabilité de bit de jeu plus uniforme pour la fonction de hachage.

Le jus
la source
12

Extrait de JDK-4045622 , où Joshua Bloch décrit les raisons pour lesquelles cette (nouvelle) String.hashCode()mise en œuvre particulière a été choisie

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

1) Tous les mots et expressions avec des entrées dans le 2ème dictionnaire intl de Merriam-Webster (311 141 chaînes, longueur moyenne 10 caractères).

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

3) Une liste d'URL collectées par un robot d'exploration qui a fonctionné pendant plusieurs heures la nuit dernière (28 372 chaînes, longueur moyenne de 49 caractères).

La mesure des performances 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 se compare 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 à l'exception de la fonction Java actuelle et des deux versions cassées de la fonction de Weinberger offrent d'excellentes performances, presque indiscernables. Je suppose fortement que cette performance est essentiellement «l'idéal théorique», ce que vous obtiendriez si vous utilisiez un véritable générateur de nombres aléatoires à 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 des fonctions beaucoup plus simples. Chacune des six fonctions restantes semble être un excellent choix, mais nous devons en choisir une. Je suppose que j'exclurais la variante de Vo et la fonction de Weinberger en raison de leur complexité supplémentaire, bien que mineure. Parmi les quatre autres, je choisirais probablement P (31), car c'est le moins cher à calculer sur une machine RISC (car 31 est la différence de deux puissances de deux). P (33) est également bon marché à calculer, mais ses performances sont légèrement inférieures et 33 est composite, ce qui me rend un peu nerveux.

Josh

Couler
la source
5

Bloch n'entre pas tout à fait dans cela, mais la raison pour laquelle j'ai toujours entendu / cru est que c'est l'algèbre de base. Les hachages se résument aux opérations de multiplication et de module, ce qui signifie que vous ne voudrez jamais utiliser des nombres avec des facteurs communs si vous pouvez l'aider. En d'autres termes, les nombres relativement premiers fournissent une distribution uniforme des réponses.

Les nombres qui composent en utilisant un hachage sont généralement:

  • module du type de données dans lequel vous le mettez (2 ^ 32 ou 2 ^ 64)
  • module du nombre de seaux dans votre table de hachage (varie. En java utilisé pour être premier, 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 de soin supplémentaire est dû.

Jason
la source
4

Dans la dernière version de JDK, 31 est toujours utilisé. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()

Le but de la chaîne de hachage est

  • unique (voir opérateur ^ dans le document de calcul du code de hachage, cela aide à unique)
  • coût pas cher pour le calcul

31 est la valeur maximale peut être mise dans un registre de 8 bits (= 1 octet), est le plus grand nombre premier peut être mis dans un registre de 1 octet, est un nombre impair.

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

Do Nhu Vy
la source
3

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

Dave L.
la source
1

C'est parce que 31 a une belle propriété - 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
yoAlex5
la source