Explicación detallada de seguimiento del código hashmap, cada paso se explica claramente y debe mejorarse

codigo de localización

Tome el siguiente ejemplo de código para realizar un seguimiento de lo que se hace exactamente

public static void main(String[] args) {
    
    
    HashMap<String, Integer> map = new HashMap<>();//第一步
    map.put("张三", 14);//第二步
    map.put("李四", 15);//第三步
    map.put("重地",1 );//第四步
    map.put("通话",1 );//第五步
}

Paso uno: inicialización

Método de inicialización new HashMap<>(): ingrese este método, pero no se crea ninguna matriz de nodos (put lo hará), desplazamiento a la derecha sin signo a <<< b, fórmula de cálculo rápido: a ∗ 2 ba*2^ba2b

// 默认的初始容量是16	1 << 4 相当于 1*2的4次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

/**
 * 构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空HashMap,size=0 。
 */
public HashMap() {
    
    
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

Paso 2: primer puesto

Ingrese map.put("张三", 14), calcule la clave: código hash de Zhang San

clave.hashCode(): 0000 0000 0000 1011 1101 0010 1110 1001 decimal 774889

h >>> 16:0000 0000 0000 0000 0000 0000 0000 1011 = 0000 0000 0000 1011 1101 0010 1110 1001 >>> 16

^Operación XOR: lo mismo es 0, diferente es 1

0000 0000 0000 1011 1101 0010 1110 1001

^

0000 0000 0000 0000 0000 0000 0000 1011


0000 0000 0000 1011 1101 0010 1110 0010 Decimales 774.882

Este es el valor hash final de Zhang San: 774,882

public V put(K key, V value) {
    
    
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
    
    
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

¿Por qué desplazarse 16 bits a la derecha? Para más hash, menos colisiones de hash

Debido a la operación de suma (longitud-1), la longitud es inferior a 2 elevado a 16 en la mayoría de los casos. Por lo tanto, siempre son los 16 bits inferiores (o incluso inferiores) del código hash los que participan en la operación. Si los 16 bits superiores también participan en la operación, el subíndice resultante tendrá más hash.

Por lo tanto, los 16 bits altos no se utilizan ¿Cómo podemos dejar que los 16 bits altos también participen en la operación? Es por eso que existe el método hash (clave de objeto). Deje que su hashCode() y su propia operación ^ alta de 16 bits. Entonces (h >>> 16) obtiene sus 16 bits altos y realiza la operación ^ con hashCode().

Ejemplo: al calcular la posición de almacenamiento de (n - 1) y elementos hash, el resultado de no desplazarse 16 bits a la derecha antes

Tomemos cualquier ejemplo, por ejemplo, los 16 bits altos son todos 1

1111 1111 1111 1111 1101 0010 1110 1001 hash

​ &

0000 0000 0000 0000 0000 0000 0000 1111 n - 1 = 16-1 = 15


0000 0000 0000 0000 0000 0000 0000 1001 9

Tomemos otro ejemplo de hash con 1 en el bit alto.

1101 1001 1001 1001 1101 0010 1110 1001 hash

​ &

0000 0000 0000 0000 0000 0000 0000 1111 n - 1 = 16-1 = 15


0000 0000 0000 0000 0000 0000 0000 1001 9

Se puede encontrar que los bits altos no pueden participar en el cálculo, lo que aumenta la tasa de conflicto de hash. Si hay un desplazamiento a la derecha de 16 para permitir que los bits altos también participen en el cálculo, el conflicto de hash se reducirá. es prepararse para cuando la longitud de la matriz sea muy pequeña.

Si n, es decir, la longitud de la matriz es muy pequeña, suponiendo que sea 16, entonces n - 1 es 1111. Dicho valor y el código hash se someten directamente a una operación AND bit a bit. De hecho, solo los últimos 4 bits del hash Se utilizan valores. Si los bits altos del valor hash cambian mucho y los bits bajos cambian muy poco, es fácil causar un conflicto hash, por lo que aquí usamos tanto los bits altos como los bajos para resolver este problema.

¿Por qué la longitud de HashMap debe ser una potencia de 2?

Debido a que el método utilizado por HashMap es muy inteligente, utiliza hash & (table.length -1) para obtener el bit de almacenamiento del objeto. Como se mencionó anteriormente, la longitud de la matriz subyacente de HashMap es siempre 2 elevado a la enésima potencia. Esta es la velocidad de optimización de HashMap. Cuando la longitud es siempre 2 elevada a la enésima potencia, la operación hash & (longitud-1) es equivalente a tomar la longitud del módulo, es decir, hash%length, pero & es más eficiente que%. Por ejemplo, n%32=n&(32-1).

3. Luego ingrese putVal(774,882, "张三", 14, false, true)y mire los comentarios del código.

/**
该表在首次使用时初始化,并根据需要调整大小。 
分配时,长度始终是 2 的幂。(我们还在某些操作中允许长度为零,以允许当前不需要的引导机制。)
*/
transient Node<K,V>[] table;

/**
此 HashMap 已在结构上修改的次数 结构修改是更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些。 该字段用于使 HashMap 的 Collection-views 上的迭代器快速失败。 (请参阅 ConcurrentModificationException)。
*/
transient int modCount;

/**
此映射中包含的键值映射的数量。
*/
transient int size;

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

            Node(int hash, K key, V value, Node<K,V> next) {
    
    
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    
    
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //1.1 第一次进来 table 为空(上面那个),为ture
    if ((tab = table) == null || (n = tab.length) == 0)
        //1.2 跳转到 第一次:运行扩容方法 resize(),扩容后现在tab为16容量的数组,每个索引(桶)都是为null
        n = (tab = resize()).length;//16
    //1.13 开始计算元素放哪个桶(索引位置)
    /*
    i = (n - 1) & hash 表示计算数组的索引赋值给i,即确定元素存放在哪个桶中。
  
    0000 0000 0000 0000 0000 0000 0000 1111		15(n - 1)
    							         	  &
    0000 0000 0000 1011 1101 0010 1110 0010		774,882(hash)
    0000 0000 0000 0000 0000 0000 0000 0010      2 
    
    p = tab[i = (n - 1) & hash]表示获取计算出的位置的数据赋值给结点p
    此时i=2,p=tab[2]=null
    */
    if ((p = tab[i = (n - 1) & hash]) == null)
        //创建一个新节点给桶tab[2]=node(张三,14),此时如下图1.0
        tab[i] = newNode(hash, key, value, null);
    else {
    
    
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        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;
        }
    }
    //1.14 刚开始为0,加完为1
    ++modCount;
    //1.15 
    //判断实际大小是否大于threshold阈值,如果超过则扩容;
    //刚开始 size=0 > threshold=16,false
    //执行完 size=1
    if (++size > threshold)
        resize();
    //1.16
    //插入后回调,LinkedHashMap中被覆盖的afterNodeInsertion方法,用来回调移除最早放入Map的对象,对HashMap毫无作用
    afterNodeInsertion(evict);
    //1.17
    return null;
}

Insertar descripción de la imagen aquí

​ Figura 1.0

Primera vez: ejecute el método de expansión resize()

Esta es la primera vez que inicializa la matriz. Inicialice una matriz de nodos con una capacidad de 16.

final Node<K,V>[] resize() {
    
    
    //1.3
    //第一次进来table,还是空
    Node<K,V>[] oldTab = table;
    //1.4
    int oldCap = (oldTab == null) ? 0 : oldTab.length;//0
    //1.5
    //threshold刚开始为0
    int oldThr = threshold;
    //1.6
    int newCap, newThr = 0;
    if (oldCap > 0) {
    
    
        if (oldCap >= MAXIMUM_CAPACITY) {
    
    
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; 
    }
    else if (oldThr > 0) 
        newCap = oldThr;
    else {
    
     
        //1.7
        //0初始阈值表示使用默认值
        newCap = DEFAULT_INITIAL_CAPACITY;//16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//12
    }
    if (newThr == 0) {
    
    
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //1.8
    threshold = newThr;//12
    @SuppressWarnings({
    
    "rawtypes","unchecked"})
    //1.9
    //创建一个16大小Node数组
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //1.10
    //新数组赋值给hashmap属性table
    table = newTab;
    //1.11 为空不进入
    if (oldTab != null) {
    
    
        for (int j = 0; j < oldCap; ++j) {
    
    
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
    
    
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else {
    
     // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
    
    
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
    
    
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
    
    
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
    
    
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
    
    
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    //1.12
    //返回
    return newTab;
}

En este punto, se completa el primer lanzamiento.

Paso 3: Poner por segunda vez.

transferirputVal(842049, "李四", 15, false, true)

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)
        n = (tab = resize()).length;
    //1.0 计算得出索引位置为1
    if ((p = tab[i = (n - 1) & hash]) == null)
        //创建这个位置的第一个节点
        tab[i] = newNode(hash, key, value, null);
    else {
    
    
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        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;
        }
    }
    //1.1 修改次数再次加1
    ++modCount;//2
    //1.2
    //1>12,false,执行完size变为2,就是键值对个数
    if (++size > threshold)
        resize();
    //1.3 与hashmap无关
    afterNodeInsertion(evict);
    //1.4 返回
    return null;
}

Después de la ejecución, la estructura de HashMap es la siguiente

Insertar descripción de la imagen aquí

​ Figura 1.1

Paso 4: Poner por tercera vez.

aun entrandoputVal(1179410, "重地", 1, false, true)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    
    
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //1.0 目前table不为空,跳过
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //1.1 计算出i=2,与"张三"位置相同,值必然不为空,跳过,此时p为"张三"节点
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
    
    
        Node<K,V> e; K k;
        /*1.3 
        判断这个桶的第一个元素是否和新节点是否相同,是赋值给e
        
        p.hash == hash:"张三"的hash值与"重地"的hash值比较,false
        (k = p.key) == key:(k="张三")=="重地",地址值不同,false
        (key != null && key.equals(k)):"重地" != null && "重地".equals("张三")),false
        不符合条件跳过
        */
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //1.4 不是树节点,跳过
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
    
    
            //1.5 遍历链表,如果到尾巴则插入,符合树化条件则树化;相等则覆盖;
            for (int binCount = 0; ; ++binCount) {
    
    
                //1.6 "张三"下个节点为null,(e=null)==null,true
                if ((e = p.next) == null) {
    
    
                    /*1.7 
                    创建一个新的结点插入到尾部,此时p.next指向的是一块新的堆内存地址,e还是null
                    插完之后map结构如图1.2所示
                    注意第四个参数next是null,因为当前元素插入到链表末尾了,那么下一个结点肯定是null。
				   */
                    p.next = newNode(hash, key, value, null);
                    //1.8 0>7,false
                    if (binCount >= TREEIFY_THRESHOLD - 1) 
                        treeifyBin(tab, hash);
                    //1.9 因为到达尾部,跳出循环
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        //2.0 跳过
        if (e != null) {
    
     
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //2.1 执行完为3
    ++modCount;
    //2.2 跳过,size执行完为3
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    //2.3
    return null;
}

Después de la ejecución, la estructura de HashMap es la siguiente

Insertar descripción de la imagen aquí

​ Figura 1.2

Paso 5: Poner por cuarta vez

El valor hash calculado es el mismo que "lugar importante", luego ingreseputVal(1179410, "通话", 1, false, true)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    
    
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //1.0 table不为空,n=16,跳过
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //1.1 算出i=2,p='张三'节点,不为空,跳过
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
    
    
        Node<K,V> e; K k;
        //1.2 p.hash('张三'),hash('通话') 不一样,key也不一样,跳过
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //1.3 不是树结构,跳过
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
    
    
            //遍历链表
            for (int binCount = 0; ; ++binCount) {
    
    
                //1.4 e这时候被下个节点赋值(第一次进来,就是第一个节点(张三)的下个节点(重地)),e=p.next="重地",跳过
                //1.7 e="重地"下个节点=null,符合条件,进入
                if ((e = p.next) == null) {
    
    
                    //1.8 创建 "重地"下个节点='通话'节点
                    p.next = newNode(hash, key, value, null);
                    //1.9 1>=7,fasle
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    //2.0 退出循环
                    break;
                }
                /*1.5 
                "重地" 和 '通话' hash 一样,true
                k("重地")和key('通话')不同,false
                (key != null && key.equals(k)),false
                不符合条件,跳过
                */
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                //1.6 p被赋值为重地节点,相当于移动遍历的指针
                p = e;
            }
        }
        //2.1 还是为空,跳过
        if (e != null) {
    
     // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //2.2 执行完为4
    ++modCount;
    //2.3 3>12,执行完为size=4
    if (++size > threshold)
        resize();
    //2.4 跳过
    afterNodeInsertion(evict);
    //2.5 返回
    return null;
}

Después de la ejecución, la estructura de HashMap es la siguiente

[La transferencia de la imagen del enlace externo falló. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-Z36LPQk4-1644454618330) (C:\Users\Administrator\Desktop\Figure 1.3. png)]

​ Figura 1.3
if (e != null) { // mapeo existente para la clave
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//2.2 Después de la ejecución, es 4
++modCount;
//2.3 3>12, después de la ejecución es size=4
if (++size > umbral)
resize();
//2.4 Saltar
afterNodeInsertion(evict);
/ /2.5 retornoretorno
nulo;
}


执行完,HashMap结构如下

![在这里插入图片描述](https://img-blog.csdnimg.cn/71b68003ab1b4bef8559b87d4b6c52ad.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARmlyZV9Ta3lfSG8=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)




​																				图1.3

Supongo que te gusta

Origin blog.csdn.net/Fire_Sky_Ho/article/details/122853488
Recomendado
Clasificación