在面试中,HashMap的重要性,咱们不用多说了,那是相当重要的,咱们深入分析这个HashMap。
首先咱们先从以下几个方面来看HashMap:1.散列表实现 2.扰动函数 3.初始化容量 4.负载因子 5.扩容元素拆分
先来看看最简单的HashMap的散列表实现:
主要思想就是,使用生成的hashcode与数组长度做与运算,使得每个元素在这个数组中都有相应的位置。以下是小傅哥画的这个过程的示意图:
上述实现存在的问题:
可能因为Hash碰撞导致链表长度比较长,这样就不能发挥hash的威力
获取数组索引ID,需要的数组长度是2的倍数,这里面又有什么含义呢
时间效率和空间效率又该如何进行平衡呢
链表越来越长,该如何进行优化
元素的不断添加,会触发扩容的流程,原先的元素在新的数组上又该如何分配
带着以上疑问,我们逐一进行解答?
HashMap有一个函数如下:
这个函数在HashMap中叫扰动函数,可以看到HashMap不是直接获取hash值,而是进行了一次扰动计算,(h=key.hashcode()^(h>>>16))
把hash值右移16位,也就正好是自己长度的一半,之后与原hash值做异或运算,这样就混合了原hash值中的高位和低位,增大了随机性。
利用这个函数能有效的避免hash冲突,以此能解决第一个问题。
在HashMap初始化的方法中,有以上这一段方法,关键的部分是这个tableSizefor方法,这个方法能计算出扩容后的数组容量。
这里面涉及到大量的移位操作,而且移位操作的效率极高,所以采用2的幂次方作为扩容后的容量,是为了性能的保证。这里解释了第二个问题。
这里面还有个负载因子DEFAULT_LOAD_FACTOR=0.75f 的存在,负载因子主要决定HashMap要进行扩容逻辑的触发点,
0.75是专家经过大量的数学数据优化过后的最佳平衡值,当然你也可以用更多的空间来换时间上性能的提升,你可以把负载因子
调小一点,以减少碰撞。这里解释了第三个问题和第四个问题
扩容之后,数据的拆分和重新规划,原来1.7中需要重新计算hash值,但是1.8中做了优化,不再需要重新计算,提升了拆分的性能,设计的还是十分巧妙的。
1.8的实现如下:
原hash值与扩容新增出来的长度(这里指的不是最后的长度喔),进行&运算,如果值等于0,则下标位置不变,否则在原来位置基础上加上新增出来的长度,这样一来就不用重新计算每一个数组中的元素了。
1.8中对于不是第一次扩容的操作,其中容量会变为原来的两倍,阈值也会变为原来的两倍。