HashMap中位运算的运用可以说是神乎其神,也很难理解,接下来让我带你理解以下三种位操作,作者不做标题党,图文并茂你一定能看懂,欢迎交流学习
- tableSizeFor()
方法,根据传入容量返回一个>=cap
的最小二的整数次幂的数
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;
}
如何实现,这里结合一个栗子cap=10
画图给你解释:(这里基本的位运算就不解释了)
首先我们明白一点,2的整数次幂的数,最高位是1,其余位都是0,例如16的二进制位:10000
,那么15就是1111
,所以反复或的操作就是不断地把每个位数都变成1,最后+1就变成了最近的二的整数次幂的数
第一次或操作:向右移动一位,最高有效位1也会向右移动一位,我们知道或操作有1就变成1,所以最高两位有效位都会变成1
第二次或操作:当再向右移动两位,相当于把第一步的高两位向后移动两位,所以或运算后,四位都是1
此时已经完成操作了,再移动四位后,结果不变(你可以尝试画一下),
最多移动16位就能完成操作,是因为int
4个子节,32位,最多移动高16位到低16位就能完成操作
所以当位运算操作完之后,(n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
这里如果不超过最大值就返回n+1,例子中返回15+1 = 16,所以这就是sizeForTable()
的算法
- 取模运算(n - 1) & hash
常用的hash函数有:直接定址,数字分析,等等(数据结构应该都学过),HashMap中使用的是初留取余法,就是取模运算,再HashMap底层使用了位运算实现取模操作,即(n - 1) & hash = hash % n
,n表示桶也就是数组的长度
位运算实现取模操作有个前提,n必须是二的整数次幂,也就回答了为什么数组容量必须是二的整数次幂
同样,我用一个栗子结合图告诉你原理:a%b=a&(b-1)
假设a=10,b=8
所以我们想取余数,就是得到移出来的三位,也就是原来1010的后三位,所以接下来求后三位,首先我们先知道一个特性:一个数和1做与运算都是本身:0&1=0,1&1=1,所以我们将b,也就是一个n位2的整数次幂的数-1就会得到n-1位全1的数,例如16:10000,15:1111
结合这两个特性,就可以得到余数
- Hash()算法,扰动函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里hash函数根据key获取散列地址,h = key.hashCode()) ^ (h >>> 16
操作意义在哪?
简单来说就是让元素在散列表中分布的更散列,减少冲突的可能性
上面的方法中,我们可以看到hash值会和长度做位运算,当数组的长度很短时,只有低位数的hashcode值能参与运算。而让高16位参与运算可以更好的均匀散列,减少碰撞,进一步降低hash冲突的几率。并且使得高16位和低16位的信息都被保留了。
大家可以打开hashmap的源码,找到hash方法,按住ctrl点击方法里的hashcode,跳转到Object类,然后可以看到hashcode的数据类型是int。int为4个字节,1个字节8个比特位,就是32个比特位,所以16是因为32对半的结果,也就是让高的那一半也来参与运算