HashMap

¿Se ha insertado HashMap en la cabecera o cola de la lista vinculada?

Se insertó en la cabeza antes de jdk1.8, y se insertó en la cola en jdk1.8.
Análisis de la posición de inserción de la lista vinculada, la clave es analizar el método de colocación de HashMap.


El código para el método put jdk1.6 es el siguiente:

public V put(K key, V value) {
     if (key == null)
         return putForNullKey(value);
     int hash = hash(key.hashCode());
     int i = indexFor(hash, table.length);
     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
         Object k;
         //如果发现key已经在链表中存在,则修改并返回旧的值
         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
             V oldValue = e.value;
             e.value = value;
             e.recordAccess(this);
             return oldValue;
         }
     }
     //如果遍历链表没发现这个key,则会调用以下代码
     modCount++;
     addEntry(hash, key, value, i);
     return null;
}

Si el valor clave correspondiente no se puede encontrar a través de la lista vinculada, se llamará al método addEntry para agregar una entrada a la lista vinculada. El enfoque se centra en cómo insertar la lista vinculada con el método addEntry. El código fuente del método addEntry es el siguiente:

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

Aquí se construye un nuevo objeto de entrada (el último parámetro del método de construcción se pasa a la lista de entrada actual), y luego la lista de entrada anterior se reemplaza directamente con este nuevo objeto de entrada. Puede suponer que este debería ser el método de inserción de cabezales. El método de construcción de entrada:

Entry( int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
}

A partir del método de construcción de nexrt = n, se puede ver que la lista vinculada original está vinculada directamente detrás del objeto Entry recién creado, y se puede concluir que se inserta en la cabeza.

El código para el método put jdk1.8 es el siguiente:

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

En jdk1.8, cuando la longitud de la lista vinculada es mayor que 8, se convertirá en un árbol rojo-negro, por lo que el código fuente se ve mucho más complicado que jdk1.6, una gran cantidad de juicio si no es para tratar la situación de los árboles rojo-negro, y vinculado Solo se insertan las siguientes líneas de código central:

for (int binCount = 0; ; ++binCount) {
    //e是p的下一个节点
    if ((e = p.next) == null) {
        //插入链表的尾部
        p.next = newNode(hash, key, value, null);
        //如果插入后链表长度大于8则转化为红黑树
        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
            treeifyBin(tab, hash);
        break;
    }
    //如果key在链表中已经存在,则退出循环
    if (e.hash == hash &&
        ((k = e.key) == key || (key != null && key.equals(k))))
        break;
    p = e;
}
//如果key在链表中已经存在,则修改其原先的key值,并且返回老的值
if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

El código en el que se inserta la lista vinculada es:

//e是p的下一个节点
if ((e = p.next) == null) {
    //插入链表的尾部
    p.next = newNode(hash, key, value, null);
    //如果插入后链表长度大于8则转化为红黑树
    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
        treeifyBin(tab, hash);
    break;
}

De este código se puede ver claramente que cuando se alcanza el final de la lista vinculada (es decir, p es el último nodo de la lista vinculada), e se asigna a nulo e ingresará este código de bifurcación, y luego se establecerá una nueva inserción de nodo utilizando el método newNode Cola
Conclusión: la cola de la lista vinculada se inserta en jdk1.8

Falta la entrada en jdk1.8. En jdk1.6, hay una clase de entrada incorporada en HashMap, que implementa la interfaz Map.Entry; mientras que en jdk1.8, la clase de entrada se ha ido y se ha convertido en la clase de nodo. Implementó la interfaz Map.Entry, que es equivalente a la entrada en jdk1.6.

Problema de bucle infinito al expandir HashMap

HashMap usa una matriz + lista vinculada. La matriz es de longitud fija. Si la lista vinculada es demasiado larga, la longitud de la matriz debe ampliarse para volver a mostrar para reducir la longitud de la lista vinculada. Si dos hilos desencadenan expansión al mismo tiempo, al mover nodos, hará que dos nodos en una lista vinculada se refieran entre sí, generando así una lista vinculada en anillo

¿Por qué la capacidad de HashMap es una potencia de 2?

public V put(K key, V value) {
        if (key == null)                
            return putForNullKey(value);     //将空key的Entry加入到table[0]中
        int hash = hash(key.hashCode());     //计算key.hashcode()的hash值,hash函数由hashmap自己实现
        int i = indexFor(hash, table.length);//获取将要存放的数组下标
        /*
         * for中的代码用于:当hash值相同且key相同的情况下,使用新值覆盖旧值(其实就是修改功能)
         */
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {//注意:for循环在第一次执行时就会先判断条件
            Object k;
            //hash值相同且key相同的情况下,使用新值覆盖旧值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                //e.recordAccess(this);
                return oldValue;//返回旧值
            }
        }
        modCount++;
        addEntry(hash, key, value, i);//增加一个新的Entry到table[i]
        return null;//如果没有与传入的key相等的Entry,就返回null
    }
/**
     * "按位与"来获取数组下标
     */
    static int indexFor(int h, int length) {
        return h & (length - 1);
    }

HashMap siempre mantiene su cubo en la enésima potencia de 2, ¿por qué es esto? El método indexFor explica este problema. Todos saben que las operaciones con bits en las computadoras son operaciones básicas, y la eficiencia de las operaciones con bits es mucho mayor que la operación de tomar el% restante.

Por ejemplo: 2 ^ n convertido a binario es 1 + n 0s, después de restar 1 es 0 + n 1s, como 16-> 10000, 15-> 01111 Luego, de acuerdo con la regla de la operación & bit, todos son 1 (verdadero ), Es 1, luego 0 ≤ el resultado después de la operación ≤15, suponiendo que h <= 15, entonces el resultado después de la operación es h, h> 15, el resultado después de la operación son los últimos cuatro bits después de la operación & El valor, al final, es el resto después del cálculo del%.

Cuando la capacidad debe ser 2 ^ n, h & (longitud-1) == h% longitud

162 artículos originales publicados · elogiados 58 · 90,000 visitas

Supongo que te gusta

Origin blog.csdn.net/ThreeAspects/article/details/105462130
Recomendado
Clasificación