Princípio de implementação de HashMap e mudanças em java8

Prefácio

A maioria dos desenvolvedores JAVA está usando Maps, especialmente HashMaps. HashMap é uma maneira simples, mas poderosa, de armazenar e recuperar dados. Mas, quantos desenvolvedores sabem como o HashMap funciona internamente?
.


1. Memória interna

A classe JAVA HashMap implementa a interface Map <K, V>. Os principais métodos dessa interface são:

  • V put (chave K, valor V)
  • V get (chave do objeto)
  • V remover (chave do objeto)
  • Boolean containsKey (chave do objeto)

HashMaps usa uma classe interna para armazenar dados: Entry <K, V>. Este item é um par de valor-chave simples, que contém dois dados adicionais:

  • Uma referência a outra entrada, de modo que o HashMap possa armazenar entradas como uma única lista vinculada
  • O valor hash que representa o valor hash da chave. O valor de hash é armazenado para evitar o cálculo de hash sempre que o HashMap precisa ser hash.

Isso faz parte da implementação do Entry no Java 7:

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

O HashMap armazena dados em várias listas de itens com link único (também chamados de baldes ou bins ). Todas as listas são registradas no array Entry (array Entry <K, V> []) e a capacidade padrão deste array interno é 16.

A figura abaixo mostra o armazenamento interno de uma instância de HashMap com uma matriz de entradas anuláveis.  Cada item pode ser vinculado a outro item para formar uma lista vinculada.
Todas as chaves com o mesmo valor hash são colocadas na mesma lista vinculada (depósito). Chaves com diferentes valores de hash podem acabar no mesmo intervalo.

Quando o usuário chama put (chave K, valor V) ou get (chave Object), esta função calculará o índice da área de armazenamento onde a entrada está localizada. Em seguida, a função percorre a lista para encontrar a entrada com a mesma chave (usando a função equals () da chave).

Para get (), a função retorna o valor associado à entrada (se a entrada existir).

Para put (chave K, valor V), se a entrada existe, a função a substitui pelo novo valor, caso contrário, ela criará uma nova entrada no início da lista de link único (de acordo com o valor da chave e do parâmetro).

O índice de bucket (lista de links) é gerado pelo mapa em 3 etapas:

  • Primeiro, ele obtém o código hash da chave .
  • Ele reorganiza o código hash para evitar a corrupção da função hash na chave que coloca todos os dados no mesmo índice (depósito) da matriz interna
  • Ele passa pelo código hash adquirido do reformador e usa o comprimento da matriz (menos 1) submetido à máscara de bits . Essa operação garante que o índice não possa ser maior que o tamanho da matriz. Você pode pensar nisso como uma função modular otimizada computacionalmente.

Este é o código-fonte JAVA 7 e 8 para o tratamento de índices:

// the "rehash" function in JAVA 7 that takes the hashcode of the key
static int hash(int h) {
    
    
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
// the "rehash" function in JAVA 8 that directly takes the key
static final int hash(Object key) {
    
    
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
// the function that returns the index from the rehashed hash
static int indexFor(int h, int length) {
    
    
    return h & (length-1);
}

Para funcionar de maneira eficaz, o tamanho do array interno precisa ser uma potência de 2. Vamos ver por quê.

Supondo que o tamanho da matriz seja 17, o valor da máscara é 16 (o tamanho é -1). A representação binária de 16 é 0 ... 010000, portanto, para qualquer valor hash H, o índice gerado pela fórmula bit a bit "H AND 16" será 16 ou 0. Isso significa que uma matriz de tamanho 17 só será usada para 2 intervalos: aquele com índice 0 e aquele com índice 16, o que não é eficiente ...

No entanto, se agora você estiver usando uma potência de 2 (como 16), a fórmula do índice bit a bit é "H AND 15". A representação binária de 15 é 0 ... 001111, portanto, a fórmula do índice pode gerar valores de 0 a 15 e a matriz de tamanho 16 é totalmente utilizada. Por exemplo:

  • Se H = 952, sua representação binária é 0..0111011 1000 , e o índice associado é 0… 0 1000  = 8
  • Se H = 1576 e sua representação binária é 0..01100010 1000 , então o índice associado é 0… 0 1000  = 8
  • Se H = 12356146, sua representação binária é 0..010111100100010100011 0010 , e o índice associado é 0 ... 0 0010 = 2
  • Se H = 59843, sua representação binária é 0..0111010011100 0011 , e o índice associado é 0… 0 0011  = 3

É por isso que o tamanho do array é uma potência de 2. Este mecanismo é transparente para o desenvolvedor: se ele escolher um HashMap de tamanho 37, o Mapa selecionará automaticamente a próxima potência de 2 após 37 (64) para o tamanho de seu array interno.

Continua...

Acho que você gosta

Origin blog.csdn.net/plqaxx/article/details/108732084
Recomendado
Clasificación