位运算那些骚操作,数不甚数,就拿HashMap来说,在求出元素位置时候通过h & (length-1) 来计算,h是key的hash值,length是数组长度。但是这个length大小是有限制的,必须是2的次幂。那为什么是2的次幂呢?其实这个还算好理解。
首先要知道&的规则,也就是双方都为1,结果才为一,如下:
接着要知道2的次幂的二进制规律,发现开头都为1,剩余全为0。
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
toBin((int)Math.pow(2,i+1));
}
}
private static void toBin(int n){
System.out.println(Integer.toBinaryString(n)) ;
}
那么按照HashMap计算规则hash & (length-1),length(是2的次幂)-1后的规律就是这样的,哦,我的天哪,真是神奇的规律。结果全为1。
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
toBin((int)Math.pow(2,i+1)-1);
}
}
private static void toBin(int n){
System.out.println(Integer.toBinaryString(n) +" 十进制=="+ n) ;
}
那现在就可以举个计算的例子了,假如h=456,length=16,那么,最终在二进制层计算是这样的,为8,也就是这个元素放入索引为8的位置。
这时候就能体现精髓了,假如hash & (不是2的次幂-1)的数,比如&上了14,那最终结果是这样的,结果虽然也是8。看似也没有问题。实则不然,14的最后一位是0,那就是说hash不论怎么横七竖八的取值,最后算出来的值就不可能是1,就导致数组中索引为1的位置就永远放不到值。在举个例子,11的二进制是1011,那么,从这个0开始,左边全部舍去,右边只要第一位带有1的数都不会被取到,如100(4),101(5),110(6),111(7)。也就是这4个空间要浪费。
或许用下面的代码段能很好的说明。发现取值能很好的分散开。
public static void main(String[] args) {
int size =16;
for (int i = 0; i < 10; i++) {
int r =new Random().nextInt(Integer.MAX_VALUE);
System.out.println((size-1)&r);
}
}
但是如hash & (不是2的次幂-1)进行计算,结果却是这样的。取值惨不忍睹
public static void main(String[] args) {
int size =16;
for (int i = 0; i < 10; i++) {
int r =new Random().nextInt(Integer.MAX_VALUE);
System.out.println((size)&r);
}
}
简而言之,必须是2的次幂,就是为了保证在真正运算时候,二进制位都为1,减少碰撞,位置分配的均匀一些。当然%也能计算索引位置,但是位运算效率高,比%快,还有人问为什么不是2的倍数,其实通过上面的计算,2的倍数的二进制数是可能有某一位是0的,这样计算的话有些索引位置还是可能放不到数据的,如果全为1的话则不然。
必须是2的次幂又牵扯出另一个问题,为什么扩容是原来的2倍?那更好理解了,也许这个动画更能说明,不断往后填0,也就是比原来的数大了一倍,这就又保证length-1的二进制位全为1。