Como um Java HashMap manipula objetos diferentes com o mesmo código de hash?


223

De acordo com o meu entendimento, penso:

  1. É perfeitamente legal que dois objetos tenham o mesmo código de hash.
  2. Se dois objetos forem iguais (usando o método equals ()), eles terão o mesmo código hash.
  3. 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 HashMapusa internamente o código de hash do objeto. Portanto, se dois objetos podem ter o mesmo código de hash, como a HashMapfaixa pode usar qual chave ele usa?

Alguém pode explicar como o HashMapinterna usa o código hash do objeto?


29
Para o registro: # 1 e # 2 estão corretos, # 3 está errado: Dois objetos que não são iguais podem ter o mesmo código de hash.
Joachim Sauer

6
# 1 e # 3 são contraditórios mesmo
Delfic

De fato, se o item 2 não for seguido, a implementação equals () (ou possivelmente o hashCode ()) estará incorreta.
Joachim Sauer

Respostas:


346

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 os equals()métodos das chaves:

  • Se duas chaves são iguais ( equals()retorna truequando você as compara), o hashCode()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.


4
você escreveu "e o mapa de hash não seria capaz de encontrar pares de valores-chave (porque será exibido no mesmo bloco)". Você pode explicar que ele vai olhar no mesmo balde, digamos que esses dois objetos iguais são t1 e t2 e são iguais e t1 e t2 têm códigos de hash h1 e h2, respectivamente. Então, quando o hashmap procuraria t1, ele procuraria no balde h1 e t2 no balde t2?
21712 Geek

19
Se duas chaves são iguais, mas seu hashCode()método retorna códigos de hash diferentes, os métodos equals()e hashCode()da classe de chave violam o contrato e você obtém resultados estranhos ao usar essas chaves em a HashMap.
Jesper

Cada bloco pode ter vários pares de valores-chave, que são usados ​​internamente na lista vinculada. Mas a minha confusão é - o que é balde aqui? Qual estrutura de dados ele usa internamente? Existe alguma conexão entre os buckets?
Ankit Sharma

1
@AnkitSharma Se você deseja realmente conhecer todos os detalhes, procure o código-fonte HashMap, que pode ser encontrado no arquivo src.zipno diretório de instalação do JDK.
Jesper

1
@ 1290 A única relação entre as chaves no mesmo bucket é que elas têm o mesmo código de hash.
Jesper

88

Sua terceira afirmação está incorreta.

É perfeitamente legal que dois objetos desiguais tenham o mesmo código de hash. É usado HashMapcomo 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.)


6
como você chegou a 2 ^ 32 objetos possíveis?
21712 Geek

5
Estou atrasado, mas para aqueles que ainda estão se perguntando: Um código hash em Java é um int, e um int tem 2 ^ 32 possíveis valores
Xerus

69

Diagrama da estrutura do HashMap

HashMapé uma matriz de Entryobjetos.

Considere HashMapapenas uma matriz de objetos.

Dê uma olhada no que Objecté isso :

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

Cada Entryobjeto representa um par de valores-chave. O campo nextse refere a outro Entryobjeto se um bucket tiver mais de um Entry.

À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 nextcampo e assim por diante. A última entrada refere-se a null.

Quando você cria um HashMapcom o construtor padrão

HashMap hashMap = new HashMap();

A matriz é criada com tamanho 16 e equilíbrio de carga padrão de 0,75.

Adicionando um novo par de valores-chave

  1. Calcular código de hash para a chave
  2. Calcular a posição hash % (arrayLength-1)onde o elemento deve ser colocado (número do balde)
  3. Se você tentar adicionar um valor com uma chave que já foi salva HashMap, o valor será substituído.
  4. Caso contrário, o elemento será adicionado ao balde.

Se o balde já tiver pelo menos um elemento, um novo será adicionado e colocado na primeira posição do balde. Seu nextcampo se refere ao elemento antigo.

Eliminação

  1. Calcular código de hash para a chave fornecida
  2. Calcular o número do balde hash % (arrayLength-1)
  3. Obtenha uma referência ao primeiro objeto Entry no bucket e, por meio do método equals, itere todas as entradas no bucket fornecido. Eventualmente, encontraremos o correto Entry. Se um elemento desejado não for encontrado, retornenull

3
Isso está errado hash % (arrayLength-1), seria hash % arrayLength. Mas na verdade é hash & (arrayLength-1) . Ou seja, porque usa potências de two ( 2^n) para o comprimento da matriz, consumindo nmenos bits significativos.
weston

Eu acho que não é uma matriz de objetos Entity, e sim uma matriz de LinkedList / Tree. E cada árvore possui internamente objetos de entidade.
você precisa saber é o seguinte

@shevchyk Por que armazenamos chave e hash? qual é a utilidade deles? Não estamos desperdiçando memória aqui?
roottraveller

O hashset usa internamente o hashmap. as regras de adição e exclusão do hashmap são válidas para o hashset?
overexchange

2
@weston não só isso, hashCode é um intque, naturalmente, pode ser negativo, fazendo módulo em um número negativo vai lhe dar um número negativo
Eugene

35

Você 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.


3
Eu encontrei a resposta fornecida pelo Jasper melhor, eu senti o blog é mais no sentido de manipulação entrevista, do que entender o conceito
Narendra N

@NarendraN Eu concordo com você.
Abhijit Gaikwad

22

Aqui está uma descrição aproximada do HashMapmecanismo, por Java 8versão (pode ser um pouco diferente do Java 6) .


Estruturas de dados

  • Tabela de
    hash O valor do hash é calculado via hash()on key e decide qual intervalo da hashtable usar para uma determinada chave.
  • Lista vinculada (individual)
    Quando a contagem de elementos em um bucket é pequena, uma lista vinculada individual é usada.
  • Árvore vermelho-preta
    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:

    • Um balde de haxixe.
      Porque tem uma propriedade hash.
    • Um nó na lista vinculada individual (portanto, também chefe da lista vinculada) .
  • 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:

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

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 thresholde loadFactor, portanto, não precisa ser definido como um campo de classe.

Pode obter a capacidade efetiva via: capacity()


Operações

  • Encontre entidade por chave.
    Primeiro encontre o intervalo pelo valor de hash e, em seguida, faça um loop na lista vinculada ou pesquise na árvore classificada.
  • Adicione entidade com chave.
    Primeiro encontre o balde de acordo com o valor de hash da chave.
    Em seguida, tente encontrar o valor:
    • Se encontrado, substitua o valor.
    • Caso contrário, adicione um novo nó no início da lista vinculada ou insira na árvore classificada.
  • Redimensionar
    Quando thresholdatingido, 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

  • get & put A
    complexidade do tempo é O(1)porque:
    • O balde é acessado via índice de matriz, portanto O(1).
    • A lista vinculada em cada intervalo é de tamanho pequeno, portanto, pode ser visualizada como O(1).
    • O tamanho da árvore também é limitado, porque aumentará a capacidade e o re-hash quando a contagem de elementos aumentar, portanto, ela pode ser vista como O(1)não O(log N).

Você pode, por favor, dar um exemplo Como a complexidade de tempo O (1) é
Jitendra

@jsroyal Isso pode explicar a complexidade mais claramente: en.wikipedia.org/wiki/Hash_table . Mas, resumindo: encontrar o intervalo de destino é O (1), porque você o encontra pelo índice em uma matriz; então, dentro de um bucket, a quantidade de elementos é pequena e, em média, um número constante, apesar do número total de elementos na hashtable inteira, portanto, procurar o elemento de destino dentro de um bucket também é O (1); assim, O (1) + O (1) = O (1).
Eric Wang

14

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.


11

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.


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

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.

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

3

dois objetos são iguais, implica que eles têm o mesmo código de hash, mas não vice-versa.

2 objetos iguais ------> eles têm o mesmo código hash

2 objetos têm o mesmo código de hash ---- xxxxx -> eles NÃO são iguais

Atualização do Java 8 no HashMap-

você faz esta operação no seu código -

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

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ção

myHashmap.put("old","old-value");

entã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)

hash XOR hash >>> 16

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

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

agora quando você executar a próxima instrução -

myHashmap.put("very-old","very-old-value");

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, a nextvariá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.


resposta incrível (y)
Sudhanshu Gaur 02/02

2

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.

insira a descrição da imagem aqui

(Fonte)


1

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.

  • Sempre que chamamos o método get (Key k) no objeto HashMap. Primeiro, ele verifica se a chave é nula ou não. Observe que só pode haver uma chave nula no HashMap.

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

            int hash = hash(hashValue)

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)


1

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:

  • Balde -> Uma Sociedade
  • HashCode -> Endereço da sociedade (sempre exclusivo)
  • Valor -> Uma Casa na Sociedade
  • Chave -> Endereço residencial.

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.

  • Nome da Sociedade SSN
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - BiologyLovers

  1. Esse SSN (chave) primeiro nos fornece um HashCode (da tabela de índice) que nada mais é do que o nome da sociedade.
  2. Agora, várias casas podem estar na mesma sociedade, portanto, o HashCode pode ser comum.
  3. Suponha que a Sociedade seja comum para duas casas, como vamos identificar para qual casa vamos, sim, usando a chave (SSN) que nada mais é do que o endereço da Casa

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.


0

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 -

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

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 é -

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

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?

É porque, 31 é um primo ímpar na forma 2 ^ 5 - 1. E o prime ímpar reduz a chance de Hash Collision

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) = 1no 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 hasHCodeas chaves “misused”e “horsemints”usar as fórmulas descritas acima, verá as duas nos dando o mesmo 1069518484. Whooaa !! Lição aprendida -

2 objetos iguais devem ter o mesmo hashCode, mas não há garantia se o hashCode corresponder, então os objetos serão iguais. Portanto, ele deve armazenar os dois valores correspondentes a "mal utilizado" e "horsemints" no intervalo 1 (1069518484% 3).

Agora o mapa de hash se parece com -

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

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 correspondente index 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.

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

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 :)


Eu acho que isso está errado: "Ele armazena chaves em uma matriz e valores em um LinkedList."
ACV

cada elemento na lista de cada bloco contém a chave e o valor, bem como a referência ao próximo nó.
ACV

0

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:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

Portanto, fica claro que o hash é usado para encontrar o "depósito" e o primeiro elemento é sempre verificado nesse depósito. Caso contrário, equalsa chave é usada para encontrar o elemento real na lista vinculada.

Vamos ver o put()método:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

É 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) & hashAqui iestá o índice em que o novo elemento será colocado (ou é o "bucket"). né o tamanho da tabmatriz (matriz de "buckets").

Primeiro, tenta-se colocar como o primeiro elemento desse "balde". Se já houver um elemento, acrescente um novo nó à lista.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.