Marque -o e, ao mesmo tempo, você pode combinar bem o HashCode () e o Equals (). HashCode () não é igual, deve ser que também seja diferente de deduzir iguais ();
Porque quando o hashmap recebe, primeiro compare o HashCode, depois compare é igual, hashcode == && é igual, ambos são verdadeiros, por isso é considerado a mesma chave
1. Visão geral do hashmap:
Hashmap é uma implementação assíncrona da interface do mapa com base na tabela de hash. Esta implementação fornece todas as operações de mapeamento opcional e permite o uso de valores nulos e teclas nulas. Esta classe não garante a ordem dos mapeamentos, especialmente não garante que o pedido durará.
2. Estrutura de dados do Hashmap:
Na linguagem de programação Java, existem duas estruturas básicas, uma é uma matriz e a outra é um ponteiro simulado (referência). O Hashmap é na verdade uma estrutura de dados "LIST LIST LIST", ou seja, uma combinação de matrizes e listas vinculadas.
Como pode ser visto na figura acima, a camada subjacente do hashmap é uma estrutura de matriz, e cada item da matriz é outra lista vinculada. Quando um novo hashmap é criado, uma matriz será inicializada.
O código -fonte é o seguinte:
/ ** * A tabela, redimensionada, conforme necessário. V Valor;
Pode-se ver que a entrada é o elemento na matriz.
3. Implementação de acesso ao hashmap:
1) armazenamento:
public V PUT (K -Key, V Value) {// HashMap permite teclas nulas e valores nulos. // Quando a chave for nula, ligue para o método Putformullkey e coloque o valor na primeira posição da matriz. if (key == NULL) retorna putformullkey (valor); int hash = hash (key.hashcode ()); int i = indexfor (hash, tabela.length); para (entrada <k, v> e = tabela [i]; e! = null; e = e.next) {objeto k; ||. Que isso existe ainda não há entrada. modCount ++; addentry (hash, chave, valor, i);
A partir do código -fonte acima, podemos ver que, quando colocamos o elemento no hashmap, primeiro recalculamos o valor de hash com base no código de hash da chave e, de acordo com o valor do hash, a posição desse elemento na matriz (isto é, subscrito ), se a matriz estiver, existem outros elementos já armazenados na posição, para que os elementos nesta posição sejam armazenados na forma de uma lista vinculada, os recém -adicionados são colocados na cabeça da corrente e a primeira adicionada Os são colocados no final da corrente. Se não houver elemento nesta posição na matriz, coloque o elemento diretamente nesta posição na matriz.
O método Addentry (hash, chave, valor, i) coloca o par de valores-chave no índice I da tabela de matriz com base no valor de hash calculado. Addentry é um método de permissões de acesso ao pacote fornecido pelo HashMap, o código é o seguinte:
Void AddEntry (int hash, K -Key, V Valor, int BucketIndex) {// Obtenha a entrada na entrada do índice BucketIndex especificado <k, v> e = tabela [BucketIndex]; Índice e deixe o novo ponto de entrada para a tabela de entrada original [bucketIndex] = nova entrada <k, v> (hash, chave, valor, e); (size ++> = limite) // Expanda o comprimento do objeto da tabela para o dobro do original. redimensionar (2 * tabela.length);
Quando o sistema decide armazenar o par de valores-chave no hashmap, o valor na entrada não é considerado e simplesmente calcula e determina o local de armazenamento de cada entrada com base na chave. Podemos tratar completamente o valor na coleção de mapas como um anexo à chave.
O método hash (int h) recalcula o hash uma vez com base no código de hash da chave. Esse algoritmo adiciona cálculos de alto bit para evitar conflitos de hash causados quando o bit baixo permanece inalterado e quando o alto bit muda.
estático int hash (int h) {h ^ = (h >>> 20) ^ (h >>> 12);
Podemos ver que, para encontrar um elemento no hashmap, precisamos encontrar a posição correspondente na matriz com base no valor de hash da chave. Como calcular esta posição é o algoritmo de hash. Como mencionado anteriormente, a estrutura de dados do Hashmap é uma combinação de matrizes e listas vinculadas, então é claro que esperamos que as posições do elemento nesse hashmap sejam distribuídas uniformemente o máximo possível, para que o número de elementos em cada posição seja apenas um. eficiência da consulta.
Para qualquer objeto, desde que seu valor de retorno hashcode () seja o mesmo, o valor do código de hash calculado pelo programa que chama o método hash (int h) é sempre o mesmo. A primeira coisa que pensamos é modular o valor do hash para o comprimento da matriz, para que a distribuição de elementos seja relativamente uniforme. No entanto, a operação "Modulo" consome muito, e isso é feito no hashmap: ligue para o método Indexfor (int h, int length) para calcular qual índice o objeto deve ser armazenado na matriz de tabela.
O código do método Indexfor (int h, int length) é o seguinte:
estático int indexfor (int h, int length) {return h & (comprimento-1);
Este método é muito inteligente. termos de velocidade. No construtor Hashmap, há o seguinte código:
capacidade int = 1;
Esse código garante que a capacidade do hashmap esteja sempre na potência n de 2 durante a inicialização, ou seja, o comprimento da matriz subjacente está sempre na potência n de 2.
Quando o comprimento é sempre para a potência N de 2, a operação de H & (Length-1) é equivalente ao comprimento do módulo, ou seja, H %de comprimento, mas e tem maior eficiência que %.
Isso parece muito simples, mas na verdade é bastante misterioso.
Supondo que os comprimentos da matriz sejam 15 e 16, e os códigos de hash otimizados sejam 8 e 9, respectivamente, o resultado após a operação é o seguinte:
h & (tabela.Length-1) Hash Tabela.Length-1
8 & (15-1): 0100 e 1110 = 0100
9 & (15-1): 0101 e 1110 = 0100
-------------------------------------------------------- -------------------------------------------------------- ------------------------------ ------------------------ -------------------------------------------------------- -------------------------------------------------------- ------ -------------------------------------------- -------------------------------------------------------- ------------------------------------
8 & (16-1): 0100 e 1111 = 0100
9 & (16-1): 0101 e 1111 = 0101
Como pode ser visto no exemplo acima: quando estão "como" com 15-1 (1110), o mesmo resultado é produzido, ou seja, eles serão posicionados na mesma posição na matriz, que cria uma colisão, 8 e 9 serão colocados na mesma posição na matriz para formar uma lista vinculada. Ao mesmo tempo, também podemos descobrir que, quando o comprimento da matriz for 15, o valor do hash será "como" com 15-1 (1110), o último bit sempre será 0 e 0001, 0011, 0101, 1001 , 1011, 0111, 0111, 1101 nunca podem armazenar elementos, e o espaço é desperdiçado bastante. A chance de colisão aumenta ainda mais e a lenta eficiência da consulta! Quando o comprimento da matriz é de 16, ou seja, quando é para o poder N de 2, o valor em cada bit do número binário obtido por 2n-1 é 1, o que faz com que a parte baixa da soma do hash original quando Está e no pouco, de modo que o bit baixo do hash da soma obtido é o mesmo, juntamente com o método de hash (int h) otimiza ainda mais o código de hash da chave e adiciona cálculos de alto bit, de modo que apenas dois valores Do mesmo valor de hash será colocado na mesma posição na matriz para formar uma lista vinculada.
Portanto, quando o comprimento da matriz é n poderes de 2, a probabilidade de que as chaves diferentes possam calcular o mesmo índice é menor, de modo que os dados serão distribuídos uniformemente na matriz, o que significa que a probabilidade de colisão é pequena, relativa, consulta em Desta vez, você não precisa atravessar a lista vinculada em um determinado local; portanto, a eficiência da consulta será maior.
De acordo com o código-fonte do método PUT acima, quando o programa tenta colocar um par de valores-chave em um hashmap, o programa primeiro determina o local de armazenamento da entrada com base no valor de retorno HashCode () da chave: se o Chave de duas entradas O valor de retorno HashCode () é o mesmo e seu local de armazenamento é o mesmo. Se as chaves dessas duas entradas retornarem verdadeiras através da comparação igual, o valor da entrada recém -adicionada substituirá o valor da entrada original na coleção, mas a chave não será substituída. Se as chaves dessas duas entradas retornarem falsas através da comparação, a entrada recém -adicionada formará uma cadeia de entrada com a entrada original na coleção, e a entrada recém -adicionada estará localizada na cabeça da cadeia de entrada - os detalhes continuam a ver A descrição do método addEntry ().
2) Leia:
public v get (chave do objeto) {if (key == null) return getFornullkey (); comprimento)]; Retorno E.Value;
Com o algoritmo de hash armazenado acima como base, é fácil entender esse código. A partir do código -fonte acima, podemos ver que, ao obter um elemento no hashmap, calcular primeiro o código de hash da chave, encontrar um elemento na posição correspondente na matriz e depois encontrar o elemento necessário na lista vinculada da posição correspondente através do método igual da chave.
3) Para resumir, o HashMap processa o valor-chave como um todo na parte inferior, e esse todo é um objeto de entrada. O hashmap subjacente usa uma matriz de entrada [] para armazenar todos os pares de valor-chave. O método igual.
4. RESIMENTO DE HASHMAP (Rehash):
Como há cada vez mais elementos no hashmap, a chance de conflito hash está cada vez mais alta, porque o comprimento da matriz é fixo. Portanto, para melhorar a eficiência da consulta, a matriz de hashmap deve ser expandida. Aparece: os dados na matriz original devem ser recalculados e colocados na nova matriz, que é redimensionada.
Então, quando o hashmap será expandido? Quando o número de elementos no hashmap excede o tamanho da matriz *LoadFactor, a matriz é expandida. Ou seja, por padrão, o tamanho da matriz é 16. Então, quando o número de elementos no hashmap excede 16*0,75 = 12, o tamanho da matriz é expandido para 2*16 = 32, ou seja, o dobro do tamanho, e depois recalculá-lo. .
5. Parâmetros de desempenho HashMap:
Hashmap contém os seguintes construtores:
Hashmap (): construa um hashmap com uma capacidade inicial de 16 e um fator de carga de 0,75.
HashMap (int InitialCapacity): Construa um hashmap com capacidade inicial e um fator de carga de 0,75.
HashMap (int InitialCapacity, Float LoadFactor): cria um hashmap com a capacidade inicial especificada e o fator de carga especificado.
O construtor básico do Hashmap, Hashmap (int InitialCapacity, Float LoadFactor), possui dois parâmetros, eles são a capacidade inicial inicial da capacidade inicial e o fator de carga do fator de carga.
InitialCapacidade: a capacidade máxima do hashmap, ou seja, o comprimento da matriz subjacente.
Factor de carga: O fator de carga do fator de carga é definido como: o número real de elementos de uma tabela de hash (n)/a capacidade de uma tabela de hash (m).
O fator de carga mede o grau de uso de um espaço na tabela de hash. Para tabelas de hash usando o método da lista vinculada, o tempo médio para encontrar um elemento é o (1+a) O fator de carga também é pequeno, os dados da tabela de hash serão muito escassos, causando um sério desperdício de espaço.
Na implementação do HashMap, a capacidade máxima do hashmap é determinada pelo campo Limite:
A cópia do código é a seguinte:
limiar = (int) (capacidade * loadFactor);
Com base na fórmula de definição do fator de carga, pode -se ver que o limite é o número máximo de elementos permitidos de acordo com o fator de carga e a capacidade. O fator de carga padrão 0,75 é uma opção equilibrada para eficiência espacial e temporal. Quando a capacidade excede essa capacidade máxima, a capacidade redimensionada do hashmap é o dobro da capacidade:
if (size ++> = limiar) redimensionar (2 * tabela.length);
6. Mecanismo Fail-Fast:
Sabemos que java.util.hashmap não é seguro para threads; portanto, se outros threads modificarem o mapa durante o processo de uso do iterador, será lançada uma concorrente que é lançada uma estratégia de falha.
Essa estratégia é implementada no código -fonte através do campo ModCount. O esperado do iterador.
HashIterator () {esperamModCount = modCount; ;
Durante o processo de iteração, determine se o ModCount e o esperado são iguais.
Observe que o ModCount é declarado como volátil, garantindo a visibilidade das modificações entre os threads.
Entrada final <k, v> nextEntry () {if (modCount! = esperamodCount) lança novo concurrentmodificationException ();
Na API do Hashmap, é declarado:
Os iteradores retornados pelo "Método de Visualização da Coleção" de todas as classes de hashmap falham rapidamente: depois que o iterador for criado, se o mapeamento for modificado a partir da estrutura, a menos que seja passada pelo método de remoção do iterador, de qualquer outra maneira Jogue uma concorrente da Modificação se a modificação for feita. Portanto, diante de modificações simultâneas, o iterador em breve falhará completamente sem arriscar comportamentos incertos arbitrários em momentos incertos no futuro.
Observe que o comportamento de falha rápida do iterador não pode ser garantida. O ITERADOR FAST FAIL lança uma concorrente de UTModificação quando funciona melhor. Portanto, é errado escrever programas que dependem dessa exceção, e a maneira correta é: o comportamento de falha rápida do iterador deve ser usada apenas para detectar erros do programa.