HashMap简介
HashMap基于哈希表的Map接口实现的,是以key-value存储形式存在的,用来存放键值对。实现是不同步的,这意味着它不是线程安全的。它的key,value都可以为null。
jdk1.8之前,哈希map是由数组加链表实现的,数组是哈希map的主体,链表主要是为了解决哈希冲突(两个对象调用的hashcode()方法计算的哈希值一致,导致计算的数组和索引相同而存在的,主要是拉链法解决了冲突。)
jdk1.8之后,以后在解决哈希冲突时有较大的变化,当链表长度大于八,并且数组长度大于64时,此时索引位置上所有的数据改为使用红黑树储存。
为什么在解决 hash 冲突的时候,不直接用红黑树?而选择先用链表,再转红黑树?
因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于 8 个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于 8 个的时候, 红黑树搜索时间复杂度是O(logn),而链表是 O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。
为什么链表改为红黑树的阈值是 8?
首先hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡。在负载因子0.75(HashMap默认)的情况下,单个hash槽内元素个数为8的概率小于百万分之一,将7作为一个分水岭,等于7时不做转换,大于等于8才转红黑树,小于等于6才转链表。链表中元素个数为8时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的。
默认加载因子是多少?为什么是 0.75,不是 0.6 或者 0.8 ?
1、负载因子是1.0
数据一开始是保存在数组里,当发生了Hash碰撞的时候,就是在这个数据节点上,生出一个链表,当链表长度达到一定长度的时候,就会把链表转化为红黑树。
当负载因子是1.0时,也就意味着,只有当数组的8个值(这个图表示了8个)全部填充了,才会发生扩容。这就带来了很大的问题,因为Hash冲突时避免不了的。
后果:当负载因子是1.0的时候,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。
因此一句话总结就是负载因子过大,虽然空间利用率上去了,但是时间效率降低了。
2、负载因子是0.5
后果:负载因子是0.5的时候,这也就意味着,当数组中的元素达到了一半就开始扩容,既然填充的元素少了,Hash冲突也会减少,那么底层的链表长度或者是红黑树的高度就会降低。查询效率就会增加。
但是,此时空间利用率就会大大的降低,原本存储1M的数据,现在就意味着需要2M的空间。
总之,就是负载因子太小,虽然时间效率提升了,但是空间利用率降低了。
3、负载因子0.75
经过前面的分析,基本上为什么是0.75的答案也就出来了,这是时间和空间的权衡。
索引计算
JDK1.8 为什么要 hashcode 异或其右移十六位的值?
因为在JDK 1.7 中扰动了 4 次,计算 hash 值的性能会稍差一点点。 从速度、功效、质量来考虑,JDK1.8 优化了高位运算的算法,通过hashCode()的高16位异或低16位实现:(h = k.hashCode()) ^ (h >>> 16)。这么做可以在数组 table 的 length 比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
为什么 hash 值要与length-1相与?
把 hash 值对数组长度取模运算,模运算的消耗很大,没有位运算快。
当 length 总是 2 的n次方时,h& (length-1) 运算等价于对length取模,也就是 h%length,但是 & 比 % 具有更高的效率。
HashMap数组的长度为什么是 2 的幂次方?
这样做效果上等同于取模,在速度、效率上比直接取模要快得多。除此之外,2 的 N 次幂有助于减少碰撞的几率。如果 length 为2的幂次方,则 length-1 转化为二进制必定是11111……的形式,在与h的二进制与操作效率会非常的快,而且空间不浪费
为什么默认初始容量必须是2的n次幂?若创建HashMap传入的initialCapacity不是2的次幂会发生什么?
因为(2的次幂数 - 1)的二进制形式表示都是1,这样在和经过异或运算的h进行按位与运算的时候才可以最多地保留其特性,减少产生哈希碰撞的概率,让数组空间均匀分配。
如果传入的initialCapacity不是2的次幂数,则HashMap会通过一通位移运算和或运算得到一个容量比传入的initialCapacity大的最小的2的次幂数,并将其作为HashMap的初始容量。例如传入7得到初始容量为8的HashMap,传入9得到初始容量为16的HashMap。