Hashmap——put()源码分析(JDK1.8)
HashMap的Demo:
Hashmap——put()源码分析(JDK1.8)

  1. 初始化插入第一个元素:
    put()方法:put的第一个参数hash(key)根据key值计算hash。
    Hashmap——put()源码分析(JDK1.8)
    hash(key)方法:当key为空,返回0;key不为空,返回h为key的hashCode值,异或h右移16位。
    扰动函数:为了保证hash的散列尽量均匀。(将高位和低位进行异或操作,让高位值的不同也能对低位的值产生影响)
    Hashmap——put()源码分析(JDK1.8)
    putVal()方法:
    Hashmap——put()源码分析(JDK1.8)
    //第一放数据,创建的Node类型的数组里是空,没有对象,newNode新建一个Node对象把tab[i]放进去。
    Hashmap——put()源码分析(JDK1.8)
    为什么容量是2的n次幂?可以用与运算代替取模运算。、

使用散列法计算时,因为2的n次幂减一的二进制每一位都是1,这时进行与(&)运算和进行模(%)运算结果是一样的,但使用&运算计算效率更高,因此容量是2的n次幂。
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
resize()方法:
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
2. 当插入第二个元素(key相同,覆盖原来的值):
Hashmap——put()源码分析(JDK1.8)
put()方法:
Hashmap——put()源码分析(JDK1.8)
putVal()方法:

Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
3.当插入第三个元素(key不相同,hash位置相同都是1):
Hashmap——put()源码分析(JDK1.8)
put()方法:
Hashmap——put()源码分析(JDK1.8)
putVal()方法:tab里是数组的长度
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
treeifyBin方法中判断所有节点的总数小于64:
Hashmap——put()源码分析(JDK1.8)
三种情况:
Hashmap——put()源码分析(JDK1.8)
resize()进行扩容:
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
扩容完成,但数组中没有任何元素。
下面迁移旧数组。
Hashmap——put()源码分析(JDK1.8)
//loHead低位头节点;loTail低位尾节点(不用换地址的链表)
//hiHead低位头节点;hiTail低位尾节点(换到高位的地址的链表)
//见流程图:第一次:e:A;next:B。
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
因为三个星星在同一个地址上,三个星星的hash值后四位相同。当扩容时,多了一位参与运算,此时有五个二进制位,我们只需要看前一位的值(倒数第五位二进制位),如果为0,与上11111,值不变,因此那还是原来的地址,如果为1,与上11111,第五位产生变化,需要加上这个1得到新数组的值(比如上图:1101+10000=13+16=29)。

Hashmap——put()源码分析(JDK1.8)
假如原来ABCDEF的地址相同。Hash值不同,因为地址相同,数组容量是8(111),后三位相同。
Hashmap——put()源码分析(JDK1.8)
Hashmap——put()源码分析(JDK1.8)
JDK1.8的HashMap是线程不安全的:不会有死循环的问题,但是扩容时会有数据覆盖的问题:当刚扩容完还没有移动元素,当前线程停止,另一个线程进行元素复制,当复制了一个元素时,原来的线程重新开始工作了,也开始进行元素的复制,这时就会发生数据覆盖。

相关文章: