HashMap,1.7与1.8的区别
底层数据结构的区别
JDK 1.8之前:
1)JDK1.8 之前HashMap 底层是数组和链表结合在一起使用也就是链表散列。
2)HashMap 通过key 的hashCode 经过扰动函数处理过后得到hash 值,然后通过(n - 1)& hash 判断当前元素存放的位置(这里的n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的hash 值以及key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
3)所谓扰动函数指的就是HashMap 的hash 方法。使用hash 方法也就是扰动函数是为了防止一些实现比较差的hashCode() 方法换句话说使用扰动函数之后可以减少碰撞。
JDK 1.8之后:
当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行resize() 方法对数组扩容。
扩容机制的区别
一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的2 倍。HashMap 的容量是有上限的,必须小于1<<30,即1073741824。如果容量超出了这个数,则不再增长,且阈值会被设置为Integer.MAX_VALUE。
JDK7 中的扩容机制:
- 空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数组。
- 有参构造函数:根据参数确定容量、负载因子、阈值等。
- 第一次put 时会初始化数组,其容量变为不小于指定容量的2 的幂数,然后根据负载因子确定阈值。
- 如果不是第一次扩容,则 新容量=旧容量x 2 ,新阈值=新容量x 负载因子。
JDK8 的扩容机制:
- 空参数的构造函数:实例化的HashMap 默认内部数组是null,即没有实例化。第一次调用put 方法时,则会开始第一次初始化扩容,长度为16。
- 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2 的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put 方法时,会将阈值赋值给容量, 然后让 阈值= 容量x 负载因子。
- 如果不是第一次扩容,则容量变为原来的2 倍,阈值也变为原来的2 倍。(容量和阈值都变为原来的2 倍时,负载因子还是不变)。此外还有几个细节需要注意:
- 首次put 时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;
- 不是首次put,则不再初始化,直接存入数据,然后判断是否需要扩容;