版权声明:本文为CSDN博主「塔奇库玛」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35296366/article/details/125792358
tableSizeFor解析
这是HashMap源码中的一个方法,这个方法的作用是找到一个离cap最近的2的n次方数
例:cap = 14,return 16;cap = 76,return 128;
static final int tableSizeFor(int cap) {
//-1可以保证当传入的数刚好是2的次方时,可以正确的返回其本身,例:传入的是16,经过下面的计算后还是返回16
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;
}
先解释一下|=、>>>这两个运算符。
- 运算符|=
n |= n 等同于 n = n | n;
| 是位运算符(或) - 运算符>>>
符号>>> 是无符号右移运算符
就是把一个二进制数右移指定位数,正数高位补0,负数高位补1;
例:
int num = ; //00000000 00000000 00000000 10111101
//无符号右移1位
num = num>>> = 1;//00000000 00000000 00000000 01011110
//把上一步得到的num再做无符号右移2位运算得到
num = num>>> = 2;//00000000 00000000 00000000 00010111
了解了以上两个运算符的作用,应该就能初步看明白源码中的tableSizeFor方法了吧。但是大部分人第一次看的时候应该都是一脸懵逼的,颇有一种我明明都看明白了每步做了什么,为什么合起来就看不懂了的感觉。下面来解释一下。
先思考一个问题:
如果给我们一个二进制数cap = 00000000 00000000 00000000 10111101这个二进制数的最近的一个2的n次方数是多少呢?学过二进制,我们可以应该可以一眼看出来这个数是cap = 00000000 00000000 00000001 00000000
我们一眼看出来了,但是程序不行,所以要思考一种办法能通过代码找到这个数。
假设我们可以把最高位1以及其后面的位都置为1,然后加1是不是就能实现这个功能了,即:
步骤1、cap = 00000000 00000000 00000000 11111111
步骤2、cap = cap + 1 = 00000000 00000000 00000001 00000000
tableSizeFor就是通过这两个步骤实现的
static final int tableSizeFor(int cap) {
//-1可以保证当传入的数是2的次方时,可以正确的返回其本身,例:传入的是16,经过下面的计算后还是返回16
//步骤一
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//步骤二n+1
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这个代码的作用就是把最高位的1后面的所有位都置为1,而且从始至终其实只用到了最高位1这1位数字参与运算,非常的巧妙,膜拜。下面由一个例子来解释一下:
有个int数n,二进制为
n = 00000000 00000001 10111010 000001101;
现在我们只看最高位的1,1后面的位数用x代替,它是0还是1都不重要(后面你会发现确实不重要),表示为:
n = 00000000 00000001 xxxxxxx xxxxxxxx;
把n做无符号右移1位运算并将结果赋值给m:
m = 00000000 00000000 1xxxxxxx xxxxxxxx;
把n与m做或运算,并将结果赋值给n,得到:
n = 00000000 00000001 1xxxxxxx xxxxxxxx;
把n做无符号右移2位运算并将结果赋值给m:
m = 00000000 00000000 011xxxxx xxxxxxxx;
把n与m做或运算,并将结果赋值给n,得到:
n = 00000000 00000001 111xxxxx xxxxxxxx;
把n做无符号右移4位运算并将结果赋值给m:
m = 00000000 00000000 0001111x xxxxxxxx;
把n与m做或运算,并将结果赋值给n,得到:
n = 00000000 00000001 1111111x xxxxxxxx;
把n做无符号右移8位运算并将结果赋值给m:
m = 00000000 00000000 00000001 1111111x ;
把n与m做或运算,并将结果赋值给n,得到:
n = 00000000 00000001 11111111 1111111x ;
把n做无符号右移16位运算并将结果赋值给m:
m = 00000000 00000000 00000000 00000001 ;
把n与m做或运算,并将结果赋值给n,得到:
n = 00000000 00000001 11111111 11111111 ;
有没有发现到不知不觉就把最高位1后面的位数全部都变成1了,而且从始至终其实只有最高位的1以及通过这个1计算得到的数参与了运算,
前面被我们表示为x的位根本没有用到。
因为java中int类型是32位的,所以5次无符号右移刚好能覆盖到32位数。同理如果是8位数只需要位移到4,如果是16位数只需要位移到8,,64位数则需要位移到32。
n |= n >>> 1;//到这一步最多可以得到2位1
n |= n >>> 2;//到这一步最多可以得到4位1
n |= n >>> 4;//到这一步最多可以得到8位1
n |= n >>> 8;//到这一步最多可以得到16位1
n |= n >>> 16;//到这一步最多可以得到32位1
把得到的n加1就能得到了一个最近的大于n的2的次方数。