Principio de implementación de HashMap y cambios en java8

Prefacio

La mayoría de los desarrolladores de JAVA están usando Maps, especialmente HashMaps. HashMap es una forma simple pero poderosa de almacenar y recuperar datos. Pero, ¿cuántos desarrolladores saben cómo funciona HashMap internamente?
.


1. Memoria interna

La clase JAVA HashMap implementa la interfaz Map <K, V>. Los principales métodos de esta interfaz son:

  • V poner (tecla K, valor V)
  • V get (clave de objeto)
  • V eliminar (tecla de objeto)
  • Boolean containsKey (clave de objeto)

HashMaps usa una clase interna para almacenar datos: Entrada <K, V>. Este elemento es un par clave-valor simple, que contiene dos datos adicionales:

  • Una referencia a otra entrada, para que HashMap pueda almacenar entradas como una sola lista enlazada
  • El valor hash que representa el valor hash de la clave. El valor hash se almacena para evitar el cálculo hash cada vez que el HashMap necesita ser hash.

Esto es parte de la implementación de Entry en Java 7:

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

HashMap almacena los datos en múltiples listas de elementos vinculados individuales (también llamados cubos o contenedores ). Todas las listas están registradas en la matriz de entrada (matriz de entrada <K, V> []) y la capacidad predeterminada de esta matriz interna es 16.

La siguiente figura muestra el almacenamiento interno de una instancia de HashMap con una matriz de entradas que aceptan valores NULL.  Cada elemento se puede vincular a otro elemento para formar una lista vinculada.
Todas las claves con el mismo valor hash se colocan en la misma lista vinculada (depósito). Las claves con diferentes valores hash pueden terminar en el mismo depósito.

Cuando el usuario llama a put (tecla K, valor V) o get (tecla Objeto), esta función calculará el índice del área de almacenamiento donde se encuentra Entry. Luego, la función recorre la lista para encontrar la Entrada con la misma tecla (usando la función igual () de la tecla).

Para get (), la función devuelve el valor asociado con la entrada (si la entrada existe).

Para put (tecla K, valor V), si la entrada existe, la función la reemplaza con el nuevo valor, de lo contrario creará una nueva entrada al principio de la lista de enlace único (según el valor en la clave y el parámetro).

El mapa genera el índice de depósito (lista de enlaces) en 3 pasos:

  • Primero obtiene el código hash de la clave .
  • Se reorganiza el código hash para prevenir la corrupción de la función hash de la clave que pone todos los datos en el mismo índice (cubo) de la matriz interna
  • Pasa a través del código hash hash adquirido por el reformador y utiliza la longitud de la matriz (menos 1) sujeta a la máscara de bits . Esta operación asegura que el índice no pueda ser mayor que el tamaño de la matriz. Puede pensar en ella como una función modular optimizada computacionalmente.

Este es el código fuente de JAVA 7 y 8 para manejar í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 que funcione con eficacia, el tamaño de la matriz interna debe ser una potencia de 2. Veamos por qué.

Suponiendo que el tamaño de la matriz es 17, el valor de la máscara es 16 (el tamaño es -1). La representación binaria de 16 es 0 ... 010000, por lo que para cualquier valor hash H, el índice generado por la fórmula bit a bit "H AND 16" será 16 o 0. Esto significa que una matriz de tamaño 17 solo se usará para 2 cubos: el que tiene el índice 0 y el que tiene el índice 16, que no es eficiente ...

Sin embargo, si ahora está usando una potencia de 2 (como 16), la fórmula de índice bit a bit es "H Y 15". La representación binaria de 15 es 0 ... 001111, por lo que la fórmula de índice puede generar valores de 0 a 15, y la matriz de tamaño 16 se utiliza por completo. P.ej:

  • Si H = 952, su representación binaria es 0..0111011 1000 , y el índice asociado es 0… 0 1000  = 8
  • Si H = 1576 y su representación binaria es 0..01100010 1000 , entonces el índice asociado es 0… 0 1000  = 8
  • Si H = 12356146, su representación binaria es 0..010111100100010100011 0010 , y el índice asociado es 0 ... 0 0010 = 2
  • Si H = 59843, su representación binaria es 0..0111010011100 0011 , y el índice asociado es 0… 0 0011  = 3

Es por eso que el tamaño de la matriz es una potencia de 2. Este mecanismo es transparente para el desarrollador: si elige un HashMap de tamaño 37, Map seleccionará automáticamente la siguiente potencia de 2 después de 37 (64) para el tamaño de su matriz interna.

Continuará...

Supongo que te gusta

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