Para expandir a resposta de David Richerby, o termo " função hash " está um pouco sobrecarregado. Freqüentemente, quando falamos de uma função hash, pensamos em MD5, SHA-1 ou algo como o .hashCode()
método Java , que transforma algumas entradas em um único número. No entanto, é muito improvável que o domínio desse número (ou seja, o valor máximo) seja do mesmo tamanho da hashtable em que você está tentando armazenar dados. (MD5 é 16 bytes, SHA-1 é 20 bytes e .hashCode()
é int
- 4 bytes).
Portanto, sua pergunta é sobre o próximo passo - uma vez que temos uma função hash que pode mapear entradas arbitrárias para números, como as colocamos em uma estrutura de dados de um tamanho específico? Com outra função, também chamada de "função hash"!
Um exemplo trivial dessa função é módulo ; você pode mapear facilmente um número arbitrário de tamanho para um índice específico em uma matriz com módulo. Isso é introduzido no CLRS como "o método de divisão":
No método de divisão para criar funções de hash, mapeamos uma chave em um dos slots, pegando o restante de dividido por . Ou seja, a função hash ékmkm
h(k)=k mod .m
...
Ao usar o método de divisão, geralmente evitamos certos valores de . Por exemplo, não deve ser uma potência de 2, pois se então é apenas os bits de de ordem mais baixa de .mmm=2ph(k)pk
~ Introdução aos Algoritmos, §11.3.1 - CLRS
Portanto, o módulo não é uma excelente função de hash, pois restringe os tamanhos que podemos usar com segurança para nossa estrutura de dados subjacente. A próxima seção apresenta um "método de multiplicação" um pouco mais complexo, que também usa módulo, mas é vantajoso porque "o valor de não é crítico". No entanto, funciona melhor com algum conhecimento prévio de "características dos dados que estão sendo hashados" - algo que geralmente não sabemos.m
O Java HashMap
usa uma versão modificada do método de divisão que executa uma etapa de pré-processamento para levar em conta .hashCode()
implementações fracas, para que ele possa usar matrizes de tamanho de dois poder. Você pode ver exatamente o que está acontecendo no .getEntry()
método (os comentários são meus):
// hash() transforms key.hashCode() to protect against bad hash functions
int hash = (key == null) ? 0 : hash(key.hashCode());
// indexOf() converts the resulting hash to a value between 0 and table.length-1
for (Entry<K,V> e = table[indexFor(hash, table.length)];
...
O Java 8 trouxe uma reescrita HashMap
ainda mais rápida, mas um pouco mais difícil de ler. Ele usa o mesmo princípio geral para pesquisa de índice, no entanto.