背景
二次幂 是指底数为2且指数为整数n的幂, 如2, 4, 8, 16等
大于等于给定数字a的最小二次幂
即输入[5, 8]的数均输出8, 输入[9, 16]的数均输出16
在Java的HashMap中, 为了方便计算hash值、减少hash碰撞, hash的范围都是二次幂, 在初始化map和map扩容时需要根据给定的初始值计算大于等于给定数字a的最小二次幂
, 即 HashMap.java
中的 int tableSizeFor(int cap)
Java中int的最大值为2^31-1, 即0x7fffffff
, 即01111111 11111111 11111111 11111111
HashMap的capacity最大值为2^30, 即1 << 30
, 即0x40000000
, 即01000000 00000000 00000000 00000000
Java8中的实现
看二进制的数, 大于给定数字a的最小二次幂
即从左到右数第一个值为1的位, 再左一位为1其他为0
5->8 0101->1000
7->8 0111->1000
目标值减1的数, 即从第一个值为1的位开始所有位的值均为1
5->7 0101->0111
7->7 0111->0111
考虑到输入即为二次幂的情况, 先将输入减1, 再计算从第一个值为1的位开始所有位的值均为1, 不需要考虑等于的情况, 最后得到的数再加1
0010 1001->0011 1111 即 41->63, 输出64
如何将一个数最高位1后面的1全部置为1呢? 使用右移+取或, 例如0100
右移1位并和自身取或0100 | 0010 = 0110
就将最高位的后一位置为了1, 不断重复这个过程
输入的最大值为1 << 30
即01000000 00000000 00000000 00000000
减1后为00111111 11111111 11111111 11111111
, 不断右移取或后还是自己, 加1后为01000000 00000000 00000000 00000000
假设输入1 << 29 + 1
即00100000 00000000 00000000 00000001
右移1位后和自己取或00110000 00000000 00000000 00000000
右移2位后和自己取或00111100 00000000 00000000 00000000
右移4位后和自己取或00111111 11000000 00000000 00000000
右移8位后和自己取或00111111 11111111 11000000 00000000
右移16位后和自己取或00111111 11111111 11111111 11111111
加1后为01000000 00000000 00000000 00000001
即1 << 30
1 | static final int MAXIMUM_CAPACITY = 1 << 30; |
Java11中的实现
目标同样是将最高位1后面的0计算出前导0的个数, 最后加1
先求出a前导0的数量, 使用比较和移位求出, 然后直接将-1 0xffffffff
进行无符号右移
其中numberOfLeadingZeros()
方法在OracleJDK中含注解@IntrinsicCandidate
, OpenJDK中含注解@HotSpotIntrinsicCandidate
, 均表示HotspotJVM会根据当前处理器平台对运算进行优化
1 |
|
其中Integer.numberOfLeadingZeros()
的实现如下
1 |
|