hashmap 代码跟进详解,每个步骤都讲清楚,待完善

跟踪代码

以下面代码示例,跟踪具体做了什么

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

第一步:初始化

初始化方法 new HashMap<>() 进来这个方法,但没有创建Node数组(put才会做),无符号右移 a <<< b ,快速计算公式: a ∗ 2 b a*2^b a∗2b

// 默认的初始容量是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;
}

第二步:第一次put

进入map.put("张三", 14),计算key:张三的 hashCode

key.hashCode() :0000 0000 0000 1011 1101 0010 1110 1001 十进制 774889

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

^异或运算:相同为0,不同为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 十进制 774,882

这就是最后张三的hash值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);
}

为什么要右移16位?为了更加散列,减少哈希冲突

由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列。

所以这样高16位是用不到的,如何让高16也参与运算呢。所以才有hash(Object key)方法。让他的hashCode()和自己的高16位^运算。所以(h >>> 16)得到他的高16位与hashCode()进行^运算。

例子:在计算(n - 1) & hash 元素存放位置的时候,之前不右移16位的结果

随便拿个例子举例,比如高16位都是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

再拿一个高位也有1的hash例子

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

可以发现,高位无法参与到计算中,使得哈希冲突率提升,如果有右移16为让高位也参与计算,将减少哈希冲突,目的是为了数组长度很小的时候做的预备

如果当 n 即数组长度很小,假设是 16 的话,那么 n - 1 即为 1111 ,这样的值和 hashCode 直接做按位与操作,实际上只使用了哈希值的后 4 位。如果当哈希值的高位变化很大,低位变化很小,这样就很容易造成哈希冲突了,所以这里把高低位都利用起来,从而解决了这个问题。

为什么HashMap的长度为什么要是2的幂?

因为HashMap使用的方法很巧妙,它通过hash & (table.length -1 )来得到该对象的保存位,前面说过HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,hash & (length-1 )运算等价于对length取模,也就是hash%length,但是&比%具有更高的效率。比如 n %32=n&(32-1)。

3.接着进入putVal(774,882, "张三", 14, false, true),接下来看代码注释

/**
该表在首次使用时初始化,并根据需要调整大小。 
分配时,长度始终是 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;
}

hashmap 代码跟进详解,每个步骤都讲清楚,待完善

​ 图1.0

第一次:运行扩容方法 resize()

这才是第一次初始化数组,初始化一个容量为16的Node数组

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;
}

至此,第一次put完成

第三步:第二次put

调用putVal(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;
}

执行完,HashMap结构如下

hashmap 代码跟进详解,每个步骤都讲清楚,待完善

​ 图1.1

第四步:第三次put

依旧是进入putVal(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;
}

执行完,HashMap结构如下

hashmap 代码跟进详解,每个步骤都讲清楚,待完善

​ 图1.2

第五步:第四次put

计算出的hash值和’重地’一样,接着进入putVal(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;
}

执行完,HashMap结构如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z36LPQk4-1644454618330)(C:\Users\Administrator\Desktop\图1.3.png)]

​ 图1.3
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;
}


执行完,HashMap结构如下

![在这里插入图片描述](https://www.icode9.com/i/ll/?i=71b68003ab1b4bef8559b87d4b6c52ad.png?,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARmlyZV9Ta3lfSG8=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)




​																				图1.3
上一篇:[ECS7天实践训练营 进阶路线] 成功案例 - 钉钉


下一篇:JavaScript变量