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