Análisis profundo del código fuente de HashMap después de JDK1.8

JDK1.8后HashMapAnálisis en profundidad del código fuente:

1. Variables de miembro:

1.1 Capacidad de inicialización: el número de cubos (16):static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
1.2 Capacidad máxima:static final int MAXIMUM_CAPACITY = 1 << 30;
1.3 Factor de carga:static final float DEFAULT_LOAD_FACTOR = 0.75f;
1.4 Umbral de árbol (el valor predeterminado es 8):static final float DEFAULT_LOAD_FACTOR = 0.75f;
1.5 El número mínimo de elementos en el árbol (64):static final int MIN_TREEIFY_CAPACITY = 64;
1.6 Elimina el árbol y devuelve el umbral de la lista enlazada (6):static final int UNTREEIFY_THRESHOLD = 6;
1.7 La tabla hash que realmente almacena los elementos:transient Node<K,V>[] table;

2. Análisis lógico

2.1 Lógica del árbol:
  • Cuando el número de elementos de la lista enlazada en un depósito es mayor o igual a 8 y el número de todos los elementos de la tabla hash supera los 64 , la estructura de la lista enlazada en el depósito se convertirá en una estructura de árbol rojo-negro . De lo contrario, solo se llevará a cabo la expansión sin arborización.
  • Beneficios: la optimización de la lista vinculada es demasiado larga, lo que conduce a una fuerte disminución en el rendimiento de búsqueda O(n)-->O(logn)y puede reducir las colisiones de hash para mejorar la seguridad
2.2 Lógica de búsqueda:
  • Primero averigüe en qué depósito se encuentra de acuerdo con su valor hash, luego busque (O(n))en la lista vinculada detrás del depósito según el valor del valor y busque en el árbol binario(O(logn))

3. Constructor

3.1 Construcción sin parámetros: factor de carga inicial
	public HashMap() {
    
    
        this.loadFactor = DEFAULT_LOAD_FACTOR; // 默认0.75
    }
3.2 Estructura de parámetros:
	//传入初始化容量
	public HashMap(int initialCapacity) {
    
    
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

	//传入初始化容量和负载因子
	public HashMap(int initialCapacity, float loadFactor) {
    
    
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
3.3 se desprende de lo anterior HashMapcomo ArrayListuna estrategia de carga igualmente diferida, la inicialización del objeto no se genera cuando la tabla hash

4. Método básico

4.1 método de venta:
    public V put(K key, V value) {
    
    
        return putVal(hash(key), key, value, false, true);
    }

	/**
     * 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 如果是false,则可以替换掉key值相同的value值
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */

putVal方法:

    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)
            //resize方法完成哈希表的初始化
            n = (tab = resize()).length;
        //判断以key值哈希后得到的下标的桶,其中是否存在元素(这里就是要保证桶的数量为2^n,因为当n位2^n时 (n-1) & hash 相当于 hash % (n-1))
        if ((p = tab[i = (n - 1) & hash]) == null)
            //将要保存的节点放在此桶的第一位置
            tab[i] = newNode(hash, key, value, null);
        else {
    
    
            Node<K,V> e; K k;
            //判断节点是否处于同一个桶并且key值完全相同
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //替换头结点
                e = p;
            //hash(key)桶中元素不为空,判断此桶是否树化
            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;
        //判断添加元素后的哈希表大小是否超过阈值(而ArrayList是先检查再添加元素)
        if (++size > threshold)
            //扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }

putValueDiagrama de lógica

Inserte la descripción de la imagen aquí

5. Preguntas frecuentes

5.1 ¿Por qué no utilizar el hashCode()valor clave calculado proporcionado por la clase Object como subíndice del depósito?
  • Básicamente, no hay colisión. En este momento, básicamente no hay diferencia entre una tabla hash y una matriz ordinaria.
5.2 ¿Por qué h >>> 16?
  • Porque el hash básicamente realiza la operación hash en los 16 bits superiores
5.3 ¿Por qué la HashMapcapacidad media es 2 ^ n?
  • Porque cuando n bits son 2 ^ n (n-1) & hash es equivalente a hash% (n-1)

Supongo que te gusta

Origin blog.csdn.net/Beer_xiaocai/article/details/100136066
Recomendado
Clasificación