HashMap源码
hash()
方法
这是扰动函数,目的是为了避免碰撞问题
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
从上面的方法可以看到key的hash值得计算方法,key的高16位不变,低16位与高16位进行异或操作作为最终的hash值
(h >>> 16,表示无符号右移16位,高位补0,任何数跟0异或都是其本身,因此key的hash值高16位不变。)
这么做的原因是与HashMap的table下标计算方法有关
n = table.length
index = (n-1) & hash ;
可以看到,hash值与长度-1发生了与操作,这样使得高位的hash全部无作用,指截取了低位的hash值,这样会导致
- 当key仅仅发生高位变动的时候就会发生hash冲突,这对Hashmap来说往往是致命的
- 这样就算散列值分布的很松散的话,只要只是取得了最后几位的话,碰撞也会很严重。如果本身散列做的不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,则碰撞会更严重
假设n=16的话,hash值就仅仅只保留了4位
通过将hashCode的高16位与低16位进行异或操作,增加了低位的随机性,减少碰撞的概率
tableSizeFor()
方法
static final int MAXIMUM_CAPACITY = 1 << 30;
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这个方法被调用的地方如下
public HashMap(int initialCapacity, float loadFactor) {
...
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
由于HashMap的capacity
都是2的幂,当在实例化HashMap实例时,如果给定了initialCapacity
,这个方法用于找到大于等于initialCapacity
的最小的2的幂
分析算法:
- 这个算法是找到最左边的1,例如数为001xxxxx然后将其右移一位变成0001xxxx再与原来的数做或操作就变成了0011xxxx
- 在将这个数右移两位000011xx,在于原来的数做或操作,就变成了001111xx
- 以此类推,最后一定会变成00111111,也就是找到最左边的1的右边包括这位全都变成了1
- 最后对结果+1,就变成了01000000,也就是大于等于这个数的2的幂
下面我们举个例子,cap = 10
:
对cap做减1操作的目的是为了针对cap已经是2的幂的情况:
- 假如cap未减1,会导致最后进行+1操作的时候,变成原来的数的2倍,例如0100->0111->1000
- 假如减1的话,0100->0011->0100