De acordo com o meu entendimento, penso:
- É perfeitamente legal que dois objetos tenham o mesmo código de hash.
- Se dois objetos forem iguais (usando o método equals ()), eles terão o mesmo código hash.
- Se dois objetos não forem iguais, eles não poderão ter o mesmo código de hash
Estou correcto?
Agora, se estiver correto, tenho a seguinte pergunta: O HashMap
usa internamente o código de hash do objeto. Portanto, se dois objetos podem ter o mesmo código de hash, como a HashMap
faixa pode usar qual chave ele usa?
Alguém pode explicar como o HashMap
interna usa o código hash do objeto?
java
hashmap
hashcode
hash-function
akshay
fonte
fonte
Respostas:
Um hashmap funciona assim (isso é um pouco simplificado, mas ilustra o mecanismo básico):
Ele possui vários "buckets" que são usados para armazenar pares de valores-chave. Cada bucket tem um número único - é isso que identifica o bucket. Quando você coloca um par de valores-chave no mapa, o mapa de hash examinará o código de hash da chave e armazenará o par no intervalo em que o identificador é o código de hash da chave. Por exemplo: O código de hash da chave é 235 -> o par é armazenado no número de bucket 235. (Observe que um bucket pode armazenar mais de um par de valor-chave).
Quando você pesquisa um valor no hashmap, fornecendo uma chave, ele primeiro olha o código de hash da chave que você forneceu. O hashmap examinará o intervalo correspondente e comparará a chave que você forneceu com as chaves de todos os pares no intervalo, comparando-os com
equals()
.Agora você pode ver como isso é muito eficiente para procurar pares de valores-chave em um mapa: pelo código de hash da chave, o mapa de hash sabe imediatamente em qual depósito procurar, para que ele só precise testar o que está nesse depósito.
Observando o mecanismo acima, você também pode ver quais requisitos são necessários
hashCode()
e osequals()
métodos das chaves:Se duas chaves são iguais (
equals()
retornatrue
quando você as compara), ohashCode()
método delas deve retornar o mesmo número. Se as chaves violarem isso, as chaves iguais poderão ser armazenadas em diferentes intervalos, e o mapa de hash não conseguirá encontrar pares de valores-chave (porque será exibido no mesmo intervalo).Se duas chaves forem diferentes, não importa se os códigos de hash são iguais ou não. Eles serão armazenados no mesmo bloco se seus códigos de hash forem os mesmos e, nesse caso, o mapa de hash será usado
equals()
para diferenciá-los.fonte
hashCode()
método retorna códigos de hash diferentes, os métodosequals()
ehashCode()
da classe de chave violam o contrato e você obtém resultados estranhos ao usar essas chaves em aHashMap
.HashMap
, que pode ser encontrado no arquivosrc.zip
no diretório de instalação do JDK.Sua terceira afirmação está incorreta.
É perfeitamente legal que dois objetos desiguais tenham o mesmo código de hash. É usado
HashMap
como um "filtro de primeira passagem" para que o mapa encontre rapidamente possíveis entradas com a chave especificada. As chaves com o mesmo código de hash são testadas quanto à igualdade com a chave especificada.Você não desejaria a exigência de que dois objetos desiguais não pudessem ter o mesmo código de hash, caso contrário, isso limitaria você a 32 objetos possíveis. (Isso também significa que tipos diferentes não podem nem usar os campos de um objeto para gerar códigos de hash, pois outras classes podem gerar o mesmo hash.)
fonte
HashMap
é uma matriz deEntry
objetos.Considere
HashMap
apenas uma matriz de objetos.Dê uma olhada no que
Object
é isso :Cada
Entry
objeto representa um par de valores-chave. O camponext
se refere a outroEntry
objeto se um bucket tiver mais de umEntry
.Às vezes, pode acontecer que os códigos de hash para 2 objetos diferentes sejam os mesmos. Nesse caso, dois objetos serão salvos em um intervalo e serão apresentados como uma lista vinculada. O ponto de entrada é o objeto adicionado mais recentemente. Este objeto se refere a outro objeto com o
next
campo e assim por diante. A última entrada refere-se anull
.Quando você cria um
HashMap
com o construtor padrãoA matriz é criada com tamanho 16 e equilíbrio de carga padrão de 0,75.
Adicionando um novo par de valores-chave
hash % (arrayLength-1)
onde o elemento deve ser colocado (número do balde)HashMap
, o valor será substituído.Se o balde já tiver pelo menos um elemento, um novo será adicionado e colocado na primeira posição do balde. Seu
next
campo se refere ao elemento antigo.Eliminação
hash % (arrayLength-1)
Entry
. Se um elemento desejado não for encontrado, retornenull
fonte
hash % (arrayLength-1)
, seriahash % arrayLength
. Mas na verdade éhash & (arrayLength-1)
. Ou seja, porque usa potências de two (2^n
) para o comprimento da matriz, consumindon
menos bits significativos.int
que, naturalmente, pode ser negativo, fazendo módulo em um número negativo vai lhe dar um número negativoVocê pode encontrar informações excelentes em http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html
Para resumir:
O HashMap trabalha com o princípio de hash
put (key, value): o HashMap armazena os objetos key e value como Map.Entry. O Hashmap aplica código de hash (chave) para obter o bucket. se houver colisão, o HashMap usa o LinkedList para armazenar o objeto.
get (key): O HashMap usa o código de hash do Key Object para descobrir a localização do bucket e, em seguida, chame o método keys.equals () para identificar o nó correto no LinkedList e retornar o objeto de valor associado para essa chave no Java HashMap.
fonte
Aqui está uma descrição aproximada do
HashMap
mecanismo, porJava 8
versão (pode ser um pouco diferente do Java 6) .Estruturas de dados
hash O valor do hash é calculado via
hash()
on key e decide qual intervalo da hashtable usar para uma determinada chave.Quando a contagem de elementos em um bucket é pequena, uma lista vinculada individual é usada.
Quando a contagem de elementos em um balde é grande, é usada uma árvore vermelho-preta.
Classes (internas)
Map.Entry
Representa uma única entidade no mapa, a entidade chave / valor.
HashMap.Node
Versão da lista vinculada do nó.
Pode representar:
Porque tem uma propriedade hash.
HashMap.TreeNode
Versão em árvore do nó.
Campos (internos)
Node[] table
A tabela de baldes (cabeçalho das listas vinculadas).
Se um bucket não contiver elementos, ele será nulo, portanto, ocupará apenas o espaço de uma referência.
Set<Map.Entry> entrySet
Conjunto de entidades.int size
Número de entidades.
float loadFactor
Indique quão cheia é permitida a tabela de hash, antes de redimensionar.
int threshold
O próximo tamanho para redimensionar.
Fórmula:
threshold = capacity * loadFactor
Métodos (internos)
int hash(key)
Calcular o hash por chave.
Como mapear hash para bucket?
Use a seguinte lógica:
Sobre capacidade
Na tabela de hash, capacidade significa a contagem de buckets, da qual pode ser obtida
table.length
.Também pode ser calculado via
threshold
eloadFactor
, portanto, não precisa ser definido como um campo de classe.Pode obter a capacidade efetiva via:
capacity()
Operações
Primeiro encontre o intervalo pelo valor de hash e, em seguida, faça um loop na lista vinculada ou pesquise na árvore classificada.
Primeiro encontre o balde de acordo com o valor de hash da chave.
Em seguida, tente encontrar o valor:
Quando
threshold
atingido, duplicará a capacidade da hashtable (table.length
) e executará um re-hash em todos os elementos para reconstruir a tabela.Isso pode ser uma operação cara.
atuação
complexidade do tempo é
O(1)
porque:O(1)
.O(1)
.O(1)
nãoO(log N)
.fonte
O código de hash determina qual intervalo para o hashmap verificar. Se houver mais de um objeto no balde, será feita uma pesquisa linear para descobrir qual item no balde é igual ao item desejado (usando o
equals()
) método.Em outras palavras, se você tiver um código de hash perfeito, o acesso ao hashmap será constante, você nunca precisará iterar por um bucket (tecnicamente você também precisaria ter MAX_INT buckets, a implementação Java poderá compartilhar alguns códigos de hash no mesmo bucket para reduzir os requisitos de espaço). Se você tem o pior código de hash (sempre retorna o mesmo número), seu acesso ao hashmap se torna linear, pois você precisa pesquisar todos os itens do mapa (todos estão no mesmo bloco) para obter o que deseja.
Na maioria das vezes, um código de hash bem escrito não é perfeito, mas é único o suficiente para fornecer acesso mais ou menos constante.
fonte
Você está enganado no ponto três. Duas entradas podem ter o mesmo código de hash, mas não podem ser iguais. Dê uma olhada na implementação do HashMap.get no OpenJdk . Você pode ver que ele verifica se os hashes são iguais e as chaves são iguais. Se o ponto três fosse verdadeiro, seria desnecessário verificar se as chaves são iguais. O código de hash é comparado antes da chave porque o primeiro é uma comparação mais eficiente.
Se você estiver interessado em aprender um pouco mais sobre isso, dê uma olhada no artigo da Wikipedia sobre resolução de colisão Open Addressing , que acredito ser o mecanismo usado pela implementação do OpenJdk. Esse mecanismo é sutilmente diferente do que o "balde" aborda uma das outras respostas mencionadas.
fonte
Portanto, aqui vemos que, se os objetos S1 e S2 têm conteúdo diferente, temos certeza de que nosso método Hashcode substituído gerará um Hashcode diferente (116232,11601) para os dois objetos. AGORA, já que existem diferentes códigos de hash, portanto, nem se preocupará em chamar o método EQUALS. Porque um Hashcode diferente GARANTE conteúdo DIFERENTE em um objeto.
fonte
Atualização do Java 8 no HashMap-
você faz esta operação no seu código -
portanto, suponha que seu código hash retorne para as duas chaves
"old"
e"very-old"
seja o mesmo. Então o que vai acontecer.myHashMap
é um HashMap e suponha que inicialmente você não especificou sua capacidade. Portanto, a capacidade padrão de acordo com o java é 16. Então, assim que você inicializou o hashmap usando a nova palavra-chave, ele criou 16 buckets. agora quando você executou a primeira instruçãoentão o hashcode for
"old"
é calculado e, como o hashcode também pode ser um número inteiro muito grande, o java fez isso internamente - (hash é hashcode aqui e >>> é a mudança à direita)para dar uma imagem maior, ele retornará algum índice, que estaria entre 0 e 15. Agora seu par de valores-chave
"old"
e"old-value"
seria convertido na variável de instância de chave e valor do objeto Entry. e esse objeto de entrada será armazenado no bucket, ou você pode dizer que em um índice específico, esse objeto de entrada seria armazenado.FYI- Entry é uma classe na interface Map- Map.Entry, com estes assinatura / definição
agora quando você executar a próxima instrução -
e
"very-old"
fornece o mesmo código hash que"old"
, portanto, esse novo par de valores-chave é novamente enviado ao mesmo índice ou ao mesmo intervalo. Mas como esse depósito não está vazio, anext
variável do objeto Entry é usada para armazenar esse novo par de valores de chave.e isso será armazenado como lista vinculada para cada objeto que tenha o mesmo código de hash, mas um TRIEFY_THRESHOLD é especificado com o valor 6. portanto, depois disso, a lista vinculada é convertida na árvore balanceada (árvore vermelho-preta) com o primeiro elemento como o raiz.
fonte
Cada objeto Entry representa um par de valores-chave. O campo a seguir se refere a outro objeto de Entrada se um intervalo tiver mais de 1 Entrada.
Às vezes, pode acontecer que códigos de hash para 2 objetos diferentes sejam iguais. Nesse caso, 2 objetos serão salvos em um bucket e serão apresentados como LinkedList. O ponto de entrada é o objeto adicionado mais recentemente. Este objeto refere-se a outro objeto com o próximo campo e um. A última entrada refere-se a nulo. Quando você cria o HashMap com o construtor padrão
A matriz é criada com o tamanho 16 e o equilíbrio de carga padrão de 0,75.
(Fonte)
fonte
O mapa de hash funciona com base no princípio de hash
O método HashMap get (Key k) chama o método hashCode no objeto key e aplica hashValue retornado à sua própria função hash estática para encontrar um local do depósito (matriz de backup) em que chaves e valores são armazenados na forma de uma classe aninhada chamada Entry (Map. Entrada). Portanto, você concluiu que, na linha anterior, a chave e o valor são armazenados no bucket como uma forma de objeto Entry. Portanto, pensar que Somente o valor é armazenado no balde não está correto e não causará uma boa impressão no entrevistador.
Se a chave for nula, as chaves nulas sempre são mapeadas para o hash 0, portanto, o índice 0.
Se a chave não for nula, ela chamará a função hash no objeto chave, consulte a linha 4 no método acima, ou seja, key.hashCode (), então, depois que key.hashCode () retorna hashValue, a linha 4 se parece com
e agora, aplica hashValue retornado em sua própria função de hash.
Podemos nos perguntar por que estamos calculando o valor de hash novamente usando hash (hashValue). A resposta é Defender contra funções hash de baixa qualidade.
Agora o valor hash final é usado para encontrar o local do depósito no qual o objeto Entry está armazenado. O objeto de entrada é armazenado no bucket dessa maneira (hash, chave, valor, bucketindex)
fonte
Não entrarei em detalhes de como o HashMap funciona, mas darei um exemplo para que possamos lembrar como o HashMap funciona relacionando-o à realidade.
Temos Key, Value, HashCode e bucket.
Por algum tempo, relacionaremos cada um deles com o seguinte:
Usando Map.get (chave):
Stevie quer chegar à casa de seu amigo (Josse), que mora em uma vila em uma sociedade VIP, que seja a JavaLovers Society. O endereço de Josse é o SSN (que é diferente para todos). Existe um índice em que descobrimos o nome da Sociedade com base no SSN. Este índice pode ser considerado um algoritmo para descobrir o HashCode.
Usando Map.put (chave, Valor)
Isso localiza uma sociedade adequada para esse Valor, localizando o HashCode e, em seguida, o valor é armazenado.
Espero que isso ajude e esteja aberto a modificações.
fonte
Vai ser uma resposta longa, pegue uma bebida e continue a ler…
Hashing é armazenar um par de valores-chave na memória que pode ser lido e gravado mais rapidamente. Ele armazena chaves em uma matriz e valores em uma LinkedList.
Vamos dizer que eu quero armazenar 4 pares de valores-chave -
Então, para armazenar as chaves, precisamos de uma matriz de 4 elementos. Agora, como mapeio uma dessas 4 chaves para 4 índices de matriz (0,1,2,3)?
Portanto, o java encontra o hashCode de chaves individuais e mapeia-as para um índice de matriz específico. Hashcode Formulas é -
Hash e garota !! Eu sei o que você está pensando. Seu fascínio por esse dueto selvagem pode fazer você perder uma coisa importante.
Por que o java multiplica por 31?
Agora, como esse código de hash é mapeado para um índice de matriz?
resposta é
Hash Code % (Array length -1)
,. Então,“girl”
é mapeado(3173020 % 3) = 1
no nosso caso. que é o segundo elemento da matriz.e o valor "ahhan" é armazenado em um LinkedList associado ao índice de matriz 1.
HashCollision - Se você tentar encontrar
hasHCode
as chaves“misused”
e“horsemints”
usar as fórmulas descritas acima, verá as duas nos dando o mesmo1069518484
. Whooaa !! Lição aprendida -Agora o mapa de hash se parece com -
Agora, se algum corpo tentar encontrar o valor da chave
“horsemints”
, o java encontrará rapidamente o hashCode, modulá-lo e começará a procurar por seu valor no LinkedList correspondenteindex 1
. Portanto, dessa forma, não precisamos pesquisar todos os quatro índices da matriz, tornando o acesso aos dados mais rápido.Mas espere, um segundo. existem 3 valores nesse indexList correspondente do array 1 da linkedList; como ele descobre qual deles foi o valor dos principais "horsemints"?
Na verdade, eu menti, quando disse que o HashMap apenas armazena valores no LinkedList.
Ele armazena o par de valores-chave como entrada do mapa. Então, na verdade, o Mapa se parece com isso.
Agora você pode ver. Enquanto percorre o LinkedList correspondente a ArrayIndex1, ele realmente compara a chave de cada entrada desse LinkedList a "horsemints" e, quando encontra um, apenas retorna o valor dele.
Espero que você tenha se divertido durante a leitura :)
fonte
Como é dito, uma imagem vale mais que 1000 palavras. Eu digo: algum código é melhor que 1000 palavras. Aqui está o código fonte do HashMap. Get método:
Portanto, fica claro que o hash é usado para encontrar o "depósito" e o primeiro elemento é sempre verificado nesse depósito. Caso contrário,
equals
a chave é usada para encontrar o elemento real na lista vinculada.Vamos ver o
put()
método:É um pouco mais complicado, mas fica claro que o novo elemento é colocado na guia na posição calculada com base no hash:
i = (n - 1) & hash
Aquii
está o índice em que o novo elemento será colocado (ou é o "bucket").n
é o tamanho datab
matriz (matriz de "buckets").Primeiro, tenta-se colocar como o primeiro elemento desse "balde". Se já houver um elemento, acrescente um novo nó à lista.
fonte