Según la documentación de Java, el código hash para un String
objeto se calcula como:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
usando
int
aritmética, dondes[i]
es el i- ésimo carácter de la cadena,n
es la longitud de la cadena e^
indica exponenciación
¿Por qué se usa 31 como multiplicador?
Entiendo que el multiplicador debería ser un número primo relativamente grande. Entonces, ¿por qué no 29, o 37, o incluso 97?
Respuestas:
Según el Java efectivo de Joshua Bloch (un libro que no se puede recomendar lo suficiente, y que compré gracias a las continuas menciones en stackoverflow):
(del Capítulo 3, Elemento 9: Anular siempre el código hash cuando anula iguales, página 48)
fuente
Como Goodrich y Tamassia señalan, si toma más de 50,000 palabras en inglés (formadas como la unión de las listas de palabras proporcionadas en dos variantes de Unix), el uso de las constantes 31, 33, 37, 39 y 41 producirá menos de 7 colisiones en cada caso. Sabiendo esto, no debería sorprendernos que muchas implementaciones de Java elijan una de estas constantes.
Casualmente, estaba leyendo la sección "códigos hash polinómicos" cuando vi esta pregunta.
EDITAR: aquí hay un enlace al libro PDF de ~ 10mb al que me refiero anteriormente. Consulte la sección 10.2 Tablas hash (página 413) de Estructuras de datos y algoritmos en Java
fuente
En (en su mayoría) procesadores antiguos, multiplicar por 31 puede ser relativamente barato. En un ARM, por ejemplo, es solo una instrucción:
La mayoría de los otros procesadores requerirían una instrucción de desplazamiento y resta por separado. Sin embargo, si su multiplicador es lento, esto sigue siendo una victoria. Los procesadores modernos tienden a tener multiplicadores rápidos, por lo que no hace mucha diferencia, siempre que 32 vaya por el lado correcto.
No es un gran algoritmo hash, pero es lo suficientemente bueno y mejor que el código 1.0 (¡y mucho mejor que la especificación 1.0!).
fuente
String.hashCode
es anterior al StrongARM que, IIRC, introdujo un multiplicador de 8 bits y posiblemente aumentó a dos ciclos para las operaciones aritméticas / lógicas combinadas con desplazamiento.Map.Entry
ha sido fijado por la especificación de serkey.hashCode() ^ value.hashCode()
a pesar de que no es ni siquiera un par no ordenado, comokey
yvalue
tienen un significado completamente diferente. Sí, eso implica queMap.of(42, 42).hashCode()
oMap.of("foo", "foo", "bar", "bar").hashCode()
, etc., son previsiblemente cero. Así que no use mapas como claves para otros mapas ...Al multiplicar, los bits se desplazan hacia la izquierda. Esto utiliza más del espacio disponible de códigos hash, lo que reduce las colisiones.
Al no utilizar una potencia de dos, los bits de orden inferior y más a la derecha también se completan, para mezclarlos con la siguiente pieza de datos que va al hash.
La expresión
n * 31
es equivalente a(n << 5) - n
.fuente
Puede leer el razonamiento original de Bloch en "Comentarios" en http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622 . Investigó el desempeño de diferentes funciones hash con respecto al "tamaño de cadena promedio" resultante en una tabla hash.
P(31)
fue una de las funciones comunes durante ese tiempo que encontró en el libro de K&R (pero incluso Kernighan y Ritchie no podían recordar de dónde provenía). Al final, básicamente tuvo que elegir uno y lo tomó,P(31)
ya que parecía funcionar lo suficientemente bien. AunqueP(33)
no fue realmente peor y la multiplicación por 33 es igualmente rápida de calcular (solo un cambio por 5 y una suma), optó por 31 ya que 33 no es primo:Por lo tanto, el razonamiento no fue tan racional como parecen implicar muchas de las respuestas aquí. Pero todos somos buenos para encontrar razones racionales después de las decisiones intestinales (e incluso Bloch podría ser propenso a eso).
fuente
En realidad, ¡37 funcionaría bastante bien! z: = 37 * x se puede calcular como
y := x + 8 * x; z := x + 4 * y
. Ambos pasos corresponden a una instrucción LEA x86, por lo que esto es extremadamente rápido.De hecho, la multiplicación con el primo aún más grande 73 se puede hacer a la misma velocidad mediante el ajuste
y := x + 8 * x; z := x + 8 * y
.Usar 73 o 37 (en lugar de 31) podría ser mejor, ya que conduce a un código más denso : las dos instrucciones LEA solo toman 6 bytes versus los 7 bytes para mover + shift + restar para la multiplicación por 31. Una posible advertencia es que Las instrucciones LEA de 3 argumentos utilizadas aquí se hicieron más lentas en la arquitectura del puente Sandy de Intel, con una latencia aumentada de 3 ciclos.
Además, 73 es el número favorito de Sheldon Cooper.
fuente
Neil Coffey explica por qué 31 se usa en Eliminar el sesgo .
Básicamente, el uso de 31 le brinda una distribución de probabilidad de bits de ajuste más uniforme para la función hash.
fuente
De JDK-4045622 , donde Joshua Bloch describe las razones por las que
String.hashCode()
se eligió esa implementación (nueva) en particularfuente
Bloch no entra en esto, pero la lógica que siempre he escuchado / creído es que se trata de álgebra básica. Los hashes se reducen a operaciones de multiplicación y módulo, lo que significa que nunca querrás usar números con factores comunes si puedes evitarlo. En otras palabras, los números relativamente primos proporcionan una distribución uniforme de las respuestas.
Los números que se componen con un hash suelen ser:
Realmente solo puede controlar un par de estos valores, por lo que se debe tener un poco de cuidado adicional.
fuente
En la última versión de JDK, 31 todavía se usa. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()
El propósito de la cadena hash es
^
en el documento de cálculo de código hash, ayuda único)31 es el valor máximo puede poner en el registro de 8 bits (= 1 byte), es el número primo más grande puede poner en el registro de 1 byte, es el número impar.
Multiplicar 31 es << 5 luego restarlo, por lo tanto, necesita recursos baratos.
fuente
No estoy seguro, pero supongo que probaron alguna muestra de números primos y descubrieron que 31 dio la mejor distribución sobre alguna muestra de cadenas posibles.
fuente
Esto se debe a que 31 tiene una buena propiedad: su multiplicación se puede reemplazar por un desplazamiento a nivel de bits que es más rápido que la multiplicación estándar:
fuente