HashMap里面的散列函数(求在数组中的下标)是什么
(length - 1) & hash
首先要知道一点,HashMap的容量大小一定是2的幂次形式(1,2,4,8…). 原因的话可以看一下上一篇文章HashMap源码解析(一) 当执行new HashMap时都执行了什么?. 大致就是第一次初始化容量的时候hashmap会初始化为比传入容量大小大的最小的二的次幂,后面扩容的时候每次乘二(实际用的是左移操作).
知道了这一点之后我们就知道length是二的次幂. 那么对于一个二的次幂减一之后的二进制表示是什么样的呢
2-1 -> 1
4-1 -> 11
8-1 -> 111
16-1 -> 1111
看出规律了吧.就是把length的二进制表示去掉了最高位然后把所有的低位都补了1
然后看一下&也就是 与 操作.每一位&0之后都会是0.如果了解计算机网络的话,应该就很容易看出这个(length-1)&hash的作用了.(length-1)实际上的作用是掩码,把所有的高位全部过滤掉.得到的余下的数一定小于length. 可以直接作为在table数组的下表了. 到这里看来,这个过滤操作本身就可以作为一个散列函数了.
但是我们最常用的散列函数不是取余数吗?其实这个操作本身得到的结果和hash%length取余的结果是一样的.我们看一下余数的定义:余数指整数除法中被除数未被除尽部分
再来看一下我们的操作我们把高位都过滤掉了,那些高位代表的数一定是length的倍数,那么余下来的小于length的数不就是余数了.所以HashMap中使用了一个位运算来巧妙且快速的求出了余数.
HashMap中的Hash函数(求hash值)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
最近阅读HashMap源码的时候看到HashMap里的hashcode对Key本身的hashcode进行了额外的操作key.hashCode()) ^ (h >>> 16);
查了一些资料看了一下这里为什么要进行这样的操作:
这个操作实际上是进行了一次扰动,可以理解为对原来的hash码又进行了一次随机化的操作来减少碰撞.
先看一下这个操作做了什么,从源码可以看出来将原来hash码的低十六位和自己的高十六位进行了一次异或操作.也就是为低十六位进行了又一次的随机操作.根据上面的介绍我们很容易想到当我们的数据量不是足够大时,基本上用到的只有hash码的后面几位,前面的都被掩码掩盖掉了(取余取掉了).
这样做的好处是可以减少碰撞,但是具体怎么减少的就不得而知了,可以参考一下扰动函数. 文章里举了个例子:
随机选取了352个字符串,在他们散列值完全没有冲突的前提下,对它们做低位掩码,取数组下标。
做了扰动操作的碰撞数有所改善,碰撞减少了10%左右.也算是很大的提高了.
顺带提一句,Jdk7中做了四次扰动操作,JDK8只做了一次.