Análisis del método put () de hashmap

Durante la entrevista reciente, un entrevistador preguntó qué hizo el método put () de hashmap. Creo que la respuesta no es lo suficientemente buena. Por la presente estudio el hashmap y lo resumo.
HashMap se utiliza principalmente para almacenar pares clave-valor, que se basa en Ha La implementación de la interfaz Map de la tabla griega es una de las colecciones Java más utilizadas.

Antes de JDK1.8, HashMap consistía en una matriz + lista vinculada. La matriz es el cuerpo principal de HashMap, y la lista vinculada existe principalmente para resolver conflictos hash ("método de cremallera" para resolver conflictos). Después de JDK1.8, hay Debido a un gran cambio, cuando la longitud de la lista vinculada es mayor que el umbral (el valor predeterminado es 8), la lista vinculada se convierte en un árbol rojo y negro para reducir el tiempo de búsqueda. No hay
Inserte la descripción de la imagen aquímuchas tonterías. Eche un vistazo al método put () de hashMap;

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

Primero mire el método hash ():

      static final int hash(Object key) {
        int h;
        // key.hashCode():返回散列值也就是hashcode
        // ^ :按位异或
        // >>>:无符号右移,忽略符号位,空位都以0补齐
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

De todos modos, de acuerdo con una serie de algoritmos para obtener un valor hash de tipo int, luego mire el método 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;
    // table未初始化或者长度为0,进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已经存在元素
    else {
        Node<K,V> e; K k;
        // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
                // 将第一个元素赋值给e,用e来记录
                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;
                }
                // 判断链表中结点的key值与插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 相等,跳出循环
                    break;
                // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                p = e;
            }
        }
        // 表示在桶中找到key值、hash值与插入元素相等的结点
        if (e != null) { 
            // 记录e的value
            V oldValue = e.value;
            // onlyIfAbsent为false或者旧值为null
            if (!onlyIfAbsent || oldValue == null)
                //用新值替换旧值
                e.value = value;
            // 访问后回调
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
    }
    // 结构性修改
    ++modCount;
    // 实际大小大于阈值则扩容
    if (++size > threshold)
        resize();
    // 插入后回调
    afterNodeInsertion(evict);
    return null;
} 

Permítanme mencionar que los subíndices almacenados en la matriz Nodo <K, V> [] se obtienen mediante (n-1) y hash, ¿cuáles son los beneficios?
Operación AND de Java:
reglas de operación: 0 & 0 = 0; 0 & 1 = 0; 1 & 0 = 0; 1 & 1 = 1;
es decir: ambos son "1" al mismo tiempo, el resultado es "1", de lo contrario es 0. Los siguientes dos ejemplos ilustran:
Inserte la descripción de la imagen aquí
Como se mencionó anteriormente, el mapa hash se mantiene principalmente por matriz En la estructura de datos, cuando la posición de los elementos en la matriz se distribuye de la manera más uniforme posible, entonces la lista vinculada en esta matriz tiene una alta probabilidad de que solo haya un elemento, por lo que es muy conveniente al recorrer los datos, así que lo que debemos hacer es Intente colocar datos con diferentes valores hash en diferentes posiciones de matriz.

Mirando la operación anterior, solo cuando los datos después de que toda la capacidad se reduce en uno es alta, entonces la probabilidad de colisión de hash después de la operación se reducirá, por lo que el tamaño de la matriz en el hashmap es un múltiplo de potencia de 2;
Combinado con el método en el hashmap inicial, para un hashmap de capacidad dada, su tamaño siempre es una potencia de 2.

 static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

Resuma brevemente el flujo del método put ():

  1. Ejecute hash (clave de objeto) para obtener un valor hash de tipo int, y luego puede encontrar la ubicación del nodo Node de acuerdo con este valor hash;

  2. Determine si la tabla está vacía o vacía para indicar que este es el primer elemento insertado, luego use resize () para la expansión de capacidad y el tamaño inicial predeterminado es 16;

  3. Si la tabla está vacía, significa que no hay colisión hash, luego inserte directamente el nodo nodo, transfiera al paso 5, de lo contrario continúe con el siguiente paso;

  4. Si la tabla no está vacía, se realizan los siguientes tres juicios:
    1. El valor hash del primer elemento en el cubo (nodo en la matriz) es igual, la clave es igual y el valor se asigna al nodo e;
    2. El valor hash no es igual, lo que indica la clave No es igual, es un árbol rojo-negro, colóquelo en el árbol y asigne el valor al nodo del nodo e;
    3.El valor hash no es igual, lo que indica que las claves no son iguales, es una lista vinculada, recorre la lista vinculada, asigna el valor al último nodo y devuelve el nodo del nodo e (esto También hay un juicio de longitud, cuando la longitud de la lista vinculada es mayor que 8, la lista vinculada debe convertirse en un árbol rojo y negro para su almacenamiento);

  5. Determine si el valor recién insertado hace que el tamaño supere el umbral y luego expanda la capacidad

El resumen anterior puede no ser muy preciso, la situación específica aún depende de los comentarios del código anterior, pero el proceso general es así.

Publicado 39 artículos originales · ganado elogios 1 · vistas 4620

Supongo que te gusta

Origin blog.csdn.net/thetimelyrain/article/details/99873881
Recomendado
Clasificación