De acordo com a documentação Java, o código de hash para um String
objeto é calculado como:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
usando
int
aritmética, ondes[i]
é o i- ésimo caractere da sequência,n
é o comprimento da sequência e^
indica exponenciação.
Por que o 31 é usado como multiplicador?
Entendo que o multiplicador deve ser um número primo relativamente grande. Então, por que não 29, 37 ou 97?
Respostas:
De acordo com o Effective Java de Joshua Bloch (um livro que não pode ser recomendado o suficiente, e que eu comprei graças a menções contínuas sobre o stackoverflow):
(do capítulo 3, item 9: sempre substitua o código de hash ao substituir igual, página 48)
fonte
Como Goodrich e Tamassia apontam, se você usar mais de 50.000 palavras em inglês (formadas como a união das listas de palavras fornecidas em duas variantes do Unix), o uso das constantes 31, 33, 37, 39 e 41 produzirá menos de 7 colisões em cada caso. Sabendo disso, não é de surpreender que muitas implementações de Java escolham uma dessas constantes.
Por coincidência, eu estava lendo a seção "códigos de hash polinomiais" quando vi essa pergunta.
EDIT: aqui está o link para o livro ~ 10mb PDF a que me refiro acima. Consulte a seção 10.2 Tabelas de hash (página 413) de estruturas de dados e algoritmos em Java
fonte
Em (principalmente) processadores antigos, multiplicar por 31 pode ser relativamente barato. Em um ARM, por exemplo, é apenas uma instrução:
A maioria dos outros processadores exigiria uma instrução separada de troca e subtração. No entanto, se o seu multiplicador for lento, isso ainda é uma vitória. Os processadores modernos tendem a ter multiplicadores rápidos, de modo que não faz muita diferença, desde que 32 sejam do lado correto.
Não é um ótimo algoritmo de hash, mas é bom o suficiente e melhor que o código 1.0 (e muito melhor que a especificação 1.0!).
fonte
String.hashCode
antecede o StrongARM que, IIRC, introduziu um multiplicador de 8 bits e possivelmente aumentou para dois ciclos para a aritmética / lógica combinada com operações de deslocamento.Map.Entry
foi corrigido pela especificação a serkey.hashCode() ^ value.hashCode()
, apesar não é mesmo um par desordenada, comokey
evalue
tem um significado completamente diferente. Sim, isso implica queMap.of(42, 42).hashCode()
ouMap.of("foo", "foo", "bar", "bar").hashCode()
etc são previsivelmente nulos. Portanto, não use mapas como chaves para outros mapas ...Ao multiplicar, os bits são deslocados para a esquerda. Isso usa mais espaço disponível dos códigos de hash, reduzindo colisões.
Por não usar uma potência de dois, os bits de ordem inferior e mais à direita também são preenchidos, para serem misturados com os próximos dados inseridos no hash.
A expressão
n * 31
é equivalente a(n << 5) - n
.fonte
Você pode ler o raciocínio original de Bloch em "Comentários" em http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622 . Ele investigou o desempenho de diferentes funções de hash em relação ao "tamanho médio da cadeia" resultante em uma tabela de hash.
P(31)
foi uma das funções comuns durante esse período que ele encontrou no livro de K&R (mas nem Kernighan nem Ritchie conseguiam se lembrar de onde vinha). No final, ele basicamente teve que escolher um e, por isso, aceitou,P(31)
pois parecia ter um bom desempenho. Mesmo queP(33)
não tenha sido realmente pior e a multiplicação por 33 seja igualmente rápida de calcular (apenas um turno por 5 e uma adição), ele optou por 31, já que 33 não é primo:Portanto, o raciocínio não era tão racional quanto muitas das respostas aqui parecem sugerir. Mas todos somos bons em apresentar razões racionais após decisões internas (e até Bloch pode estar propenso a isso).
fonte
Na verdade, 37 funcionaria muito bem! z: = 37 * x pode ser calculado como
y := x + 8 * x; z := x + 4 * y
. As duas etapas correspondem a uma instrução LEA x86, portanto, isso é extremamente rápido.De fato, a multiplicação com o primo 73 ainda maior pode ser feita na mesma velocidade, configurando
y := x + 8 * x; z := x + 8 * y
.Usar 73 ou 37 (em vez de 31) pode ser melhor, porque leva a um código mais denso : As duas instruções LEA levam apenas 6 bytes vs. 7 bytes para mover + shift + subtrair para a multiplicação por 31. Uma ressalva possível é que as instruções LEA de três argumentos usadas aqui se tornaram mais lentas na arquitetura Sandy bridge da Intel, com uma latência aumentada de 3 ciclos.
Além disso, 73 é o número favorito de Sheldon Cooper.
fonte
Neil Coffey explica por que o 31 é usado na solução do problema .
Basicamente, o uso de 31 fornece uma distribuição de probabilidade de bits mais uniforme para a função hash.
fonte
No JDK-4045622 , onde Joshua Bloch descreve os motivos pelos quais essa (nova)
String.hashCode()
implementação específica foi escolhidafonte
Bloch não entra nisso, mas a lógica que sempre ouvi / acreditei é que essa é a álgebra básica. Os hashes se resumem às operações de multiplicação e módulo, o que significa que você nunca deseja usar números com fatores comuns, se puder ajudá-lo. Em outras palavras, números relativamente primos fornecem uma distribuição uniforme de respostas.
Os números que compõem usando um hash são geralmente:
Você realmente só consegue controlar alguns desses valores; portanto, é necessário um cuidado extra.
fonte
Na versão mais recente do JDK, 31 ainda é usado. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()
O objetivo da cadeia de hash é
^
no documento de cálculo de código de hash, ele ajuda exclusivo)31 é o valor máximo pode colocar no registro de 8 bits (= 1 byte), é o maior número primo pode colocar no registro de 1 byte, é um número ímpar.
Multiplicar 31 é << 5 e subtrai-se, portanto, precisa de recursos baratos.
fonte
Não tenho certeza, mas acho que eles testaram alguma amostra de números primos e descobriram que 31 deu a melhor distribuição em algumas amostras de possíveis Strings.
fonte
Isso ocorre porque 31 possui uma boa propriedade - sua multiplicação pode ser substituída por um deslocamento bit a bit mais rápido que a multiplicação padrão:
fonte