导读:我记得在《编程珠玑》一书中,作者提到了总有一些优秀的编程思想会让人忍不住发出“啊哈”的声音,这有可能是叹为观止,也有可能是恍然大悟。
1. 简介
tableSizeFor是Java的hashmap/concurrenthashmap的源码中比较重要的一个函数,其功能是:返回一个比输入值大或相等的,离该值最近的2的整数次幂
。举例:输入值是5,则该函数返回值是8(2的3次幂),输入值是4,则返回4。说实话,在我真正理解这个函数之前,如果你把它的源码放到我面前,让我说出这个函数的功能,我可能真的无法说出答案。在我花时间理解之后,我是忍不住内心发出了“啊哈”的赞叹!
如果你感兴趣,可以先不看后面的介绍,然后对照我上面所说的功能,看看自己是如何实现的?
OK,如果你已经写好了,那让我们看看java原作者是怎么实现的吧?话不多说,先上源码,本文中使用的是JDK11的源码。
private static final int tableSizeFor(int c) {
int n = -1 >>> Integer.numberOfLeadingZeros(c - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
2. 剖析过程
如果你看完上面的几行代码后瞬间就理解了,那么恭喜你,我已经献上了我的膝盖,后面也没必要继续看了。否则的话,我们就试着一起来理解一下吧~~
2.1 -1的二进制表示
- 取原码:00000000 00000000 00000000 00000001
- 求反码(逐位求反):11111111 11111111 11111111 11111110
- 得补码(反码+1):11111111 11111111 11111111 11111111
也就是说-1的二进制表示是:11111111 11111111 11111111 11111111
,事实上上面的过程就是求一个负数二进制表示方法的过程。
2.2 位运算
java中常见的位运算包括:按位与&、按位或|、按位非~、按位异或^,也包括>>(右移位),<<(左移位)。
右移位符号>> N通常理解为除以2的N次幂,左移<< N可理解为乘以2的N次幂。那>>>又是几个意思呢?看着也是移位。
- >>:带符号右移,正数右移后高位补0,负数右移后高位补1,因此4>>1 = 2 ,-4>>1 = -2。
- >>>:无符号右移,不管正数还是负数,右移后高位都补0。
因此,对于正数来说,二者返回值一样,但如果是负数,则相差很远,如果你是想实现除以2的效果,那么请用>>吧。
2.3 算法核心
核心在于用“二进制”来思考问题,而不是十进制,对于任意一个整数,换算为二进制之后(以5为例是0101,高位的0省略),那么比它大的2的整数次幂最小的一个值即是高一位置1,其他位置0(以5为例是1000,换算为二进制是8)。那么怎么实现这个想法呢?
作者巧妙的使用了-1进行>>>操作,右移的位数使用了Integer.numberOfLeadingZeros(c - 1),比如c是5,则0100(5-1)前面的0的位数是29,将-1>>>29位得到的是0111;然后再将结果+1,得到1000(8),就是我们期望的结果啦~
先减1再加1这个简直是神操作,既能刚好得到一个2的整数幂的值,而且能避免边界情况,即输入值本身就是2的整数幂的情况,比如输入值为4的时候,如果不减1操作,那么得到的就是8了。
好了,tableSizeFor就解析到这儿了,不知道你啊哈了没?