526 votes

Pourquoi la fonction hashCode() de Java dans String utilise-t-elle 31 comme multiplicateur ?

D'après la documentation de Java, le [code de hachage](http://java.sun.com/javase/6/docs/api/java/lang/String.html#hashCode()) pour un String est calculé comme suit :

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

en utilisant int arithmétique, où s[i] est le i e 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 doit être un nombre premier relativement grand. Alors pourquoi pas 29, ou 37, ou même 97 ?

1 votes

Comparez également stackoverflow.com/questions/1835976/ - Je pense que 31 est un mauvais choix si vous écrivez vos propres fonctions hashCode.

10 votes

Si c'était 29, ou 37, ou même 97, vous demanderiez "pourquoi pas 31 ?".

2 votes

@EJP il est important de connaître la raison derrière le choix d'un numéro, à moins que ce numéro ne soit le résultat d'un tour de magie noire.

439voto

matt b Points 73770

Selon l'ouvrage de Joshua Bloch Java efficace (un livre qu'on ne saurait trop recommander, et que j'ai acheté grâce aux mentions continuelles sur stackoverflow) :

La valeur 31 a été choisie parce que c'est un nombre premier impair. Si elle était paire et que la multiplication débordait, des informations seraient perdues, car la multiplication par 2 est équivalente à un décalage. L'avantage d'utiliser un nombre premier est moins clair, mais il est traditionnel. Une propriété intéressante 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 VM modernes effectuent ce type d'optimisation automatiquement.

(extrait du chapitre 3, point 9 : Toujours remplacer le hashcode lorsque vous remplacez les égaux, page 48)

377 votes

Eh bien, tous les nombres premiers sont impairs, sauf 2. Je dis ça comme ça.

40 votes

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 impair ET parce qu'il était premier (ET parce qu'il peut facilement être optimisé en un décalage/soustraction).

52 votes

Le 31 a été choisi parce que c'est un chiffre impair. Cela n'a aucun sens. Je dis que 31 a été choisi parce qu'il donne la meilleure distribution. computinglife.wordpress.com/2008/11/20/

85voto

jJack Points 1139

Goodrich et Tamassia ont calculé, à partir de plus de 50 000 mots anglais (formés par l'union des listes de mots fournies dans deux variantes d'Unix), que l'utilisation des constantes 31, 33, 37, 39 et 41 produira moins de 7 collisions dans chaque cas. C'est peut-être la raison pour laquelle tant d'implémentations Java choisissent ces constantes.

Voir la section 9.2 Tables de hachage (page 522) du document Structures de données et algorithmes en Java .

6 votes

Notez cependant que vous risquez d'obtenir beaucoup plus de collisions si vous utilisez un jeu de caractères international avec des caractères communs en dehors de la gamme ASCII. En tout cas, j'ai vérifié cela pour le 31 et l'allemand. Je pense donc que le choix du 31 est cassé.

59voto

Sur les anciens processeurs (pour la plupart), la multiplication par 31 peut être relativement bon marché. Sur un ARM, par exemple, cela ne représente qu'une seule 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 grand algorithme de hachage, mais c'est suffisamment bon et meilleur que le code 1.0 (et bien meilleur que la spécification 1.0 !).

7 votes

Il est amusant de constater que la multiplication par 31 est, sur mon ordinateur de bureau, un peu plus lente que la multiplication par, disons, 92821. Je suppose que le compilateur essaie de l'"optimiser" en shift et en add :-)

1 votes

Je ne pense pas avoir jamais 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 l'effet malheureux 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 encore meilleur (blenderize bits un peu mieux).

0 votes

@supercat Pas convaincu par le moins. On dirait que tu vas retourner vers les zéros. / String.hashCode est antérieure au StrongARM qui, IIRC, a introduit un multiplicateur de 8 bits et éventuellement augmenté à deux cycles pour les opérations combinées arithmétiques/logiques avec décalage.

38voto

erickson Points 127945

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

En n'utilisant pas de puissance de deux, les bits d'ordre inférieur, les plus à droite, sont également remplis, pour être mélangés avec la prochaine donnée entrant dans le hachage.

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

23voto

hrr Points 634

En fait, 37 ferait très bien l'affaire ! z := 37 * x peut être calculé comme suit y := x + 8 * x; z := x + 4 * y . Ces deux étapes correspondent à une instruction LEA x86, ce qui est donc extrêmement rapide.

En fait, la multiplication par un nombre premier encore plus grand 73 pourrait se faire à la même vitesse en mettant y := x + 8 * x; z := x + 8 * y .

Utiliser 73 ou 37 (au lieu de 31) pourrait être mieux, car cela conduit à code plus dense : Les deux instructions LEA ne prennent que 6 octets contre les 7 octets de move+shift+subtract 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 Sandy bridge d'Intel, avec une latence accrue de 3 cycles.

D'ailleurs, 73 est le numéro préféré de Sheldon Cooper.

11 votes

@Mainguy C'est en fait la syntaxe ALGOL et elle est utilisée assez souvent dans le pseudo-code.

4 votes

Mais en assembleur ARM la multiplication par 31 peut être faite en une seule instruction

5 votes

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X