HashMap在JDK1.7和JDK1.8的区别
一.区别
- 存储结构不同:JDK1.7是数组+链表,JDK1.8则是数组+链表+红黑树结构;
- 初始化方式不同:JDK1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而JDK1.8则是直接调用resize()扩容;
- 插入数据方式不同:插入键值对的put方法的区别,JDK1.8中会将节点插入到链表尾部,而JDK1.7中是采用头插,因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。;
- hash值计算不同:JDK1.7采用9次扰动(4次位运算+5次异或运算),JDK1.8采用2次扰动(1次位运算+1次异或运算)
- 扩容时JDK1.8会保持原链表的顺序,而JDK1.7会颠倒链表的顺序;而且JDK1.8是在元素插入后检测是否需要扩容,JDK1.7则是在元素插入前;
- 扩容后数据存储位置的计算方式不同:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)
- 扩容策略不同:JDK1.7中是只要不小于阈值就直接扩容2倍;而JDK1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数大于6就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。
二.JDK1.8put、get方法源码解读
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
1)put方法,直接调用putVal:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
putVal方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义存放元素的节点数组,节点,变量n,i;
Node<K,V>[] tab; Node<K,V> p; int n, i;
/*
步骤1:判断table是否为空
1)table 表示存储在Map中的元素的数组
2)(tab = table) == null 表示将table赋值给tab,并且判断tab是否为null。
3)(n = tab.length) == 0 表示,将tab的长度赋值给n,并判断n 是否等于-
*/
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 步骤2:计算index,并对null做处理