1、hashmap的底层数据结构
(1)、1.7数组+链表,1.8数组+链表+红黑树(加入红黑树提升查询效率)
(2)、1.7采用头插法、Entry节点,1.8采用尾插法、node节点,实现了Entry接口(附图)

存在问题:1.7采用头插法,在多线程并发操作下可能会产生死循环(在两个线程都已经创建了扩容数据,在调用transfer方法进行数据迁移的时候有可能会产生死循环)
死循环过程:
1>、两个线程准备进行数据迁移

2>、线程1完后了数据迁移,线程2的变量也跟了过去

3>、线程2开始根据线程1的数据进行迁移,一开始的数据已经消失

4>、线程2第一次循环执行执行以下关键代码
e.next = newTable[i]; 首先e2的next去新数据上去确定位置
newTable[i] = e; 把e2的数据迁移过去
e = next;然后e重新找到next
结果如下:

5>、线程2第二次循环执行执行以下关键代码
执行 Entry<K,V> next = e.next;

执行
e.next = newTable[i];此时e.next正好指向3,不需要变动
newTable[i] = e; 把数据2移过去
e = next;把next赋给e

6>、线程2第三次循环执行执行以下关键代码
在执行 了Entry<K,V> next = e.next后next已经成了null
e.next = newTable[i];此时newTable[i]如果正好是2的话就会形成死循环

死循环过程1.8解决:
扩容后,新数组中的链表顺序依然与旧数组中的链表顺序保持一致
2、hashmap的相关数值(加载因子、2倍扩容、数据长度大于64链表长度大于8转红黑树)
加载因子:0.75
空间换时间,如果加载因子是1的话,离散度低,hash碰撞严重,如果加载因子过于小浪费空间
2倍扩容:(如果手动设置一个容量值,不是2的指数幂,底层会默认帮我们转成最接近2的指数次幂的一个值)
原因一:利用n % m进行确定位置,当m的值是二的次幂的时候,它可以由 n & (m-1)替代,后者的效率也是更高。
原因二:假如原始长度16,对应的确定位置的二进制数1111,两倍扩容之后变成11111加一位,所以扩容之后,冲突位置的值是否需要移动,就看扩容位对应的h的二进制位是0还是1,是0则原位置不变,是1则移至新空间对应位置。所以这里有2分之1的可能会将原数据移动!
图解:

移动的元素的新的下标值都不需要从新计算,只是用原来的下标+原来的容量即可得到,移动数据量也是最少的,离散平衡也能依旧保持。
数组长度大于64链表长度大于8转红黑树:
底层源码图解:

如上可知,树形化的条件不是单纯的链表长度大于8,还得要求是容量不能小于64,否则会用扩容手段减少链表长度。
3、hashmap的存数、找数
(1)、如果key为null,则默认对应的数组下标为0;
(2)、key不为null时,根据key计算hashcode值
(3)、位置确定-防hash碰撞算法
1>、h = hashCode ^ (hashCode >>> 16);hashcode值右移16位与原hashcode值进行异或运算
2>、位置:index=h & (cap-1);
直接用hashcode跟数组长度取模计算位置的问题
直接使用hashCode & (cap - 1),当数组长度比较小的时候,hashCode值只有它的低位才会参与求取下标的计算,而一旦我们所需要存储的数据的key整体的hashCode值都比较大、并且低位都相同的时候,那么直接使用这个hashCode值来对数组长度取模,那产生的冲突将是非常恐怖的
解决办法:先获取了一个更具有代表这个数据整体的数值h(h = hashCode ^ (hashCode >>> 16))这样操作之后就将hashCode值的高低16位都运用了起来
图解:
1>>、如下,当直接使用hashCode来取模时,因为数组长度比较小,就可能存在多个hashCode对应一个模长。这不是一个合格的离散算法!

只有低位参与运算,hash碰撞相当严重
2>>、我们改改,也就是HashMap中的正确的取模算法,让高低16位都参与取模运算,图解如下。
上图第一个数,最终取模如下:

第二个数,最终取模如下:

运用该算法,高低位都参与运算,问题解决!!!