HashMap源码解析系列(二)之HashMap#tableSizeFor()方法

324 阅读8分钟
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;
}
//分析
//第一句 int n = cap - 1; 先不分析 分析完下面的算法再来说比较容易理解
1.n |= n >>> 1;  //对n二进制进行无符号右移1位
//设:n = 9
0000 0000 0000 0000 0000 0000 0000 1001  = 9
//无符号右移1位
0000 0000 0000 0000 0000 0000 0000 0100
//或 有一的为一没一的为0
-----------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1101
==================================================================
//无符号右移2位
0000 0000 0000 0000 0000 0000 0000 0011
//或 有一的为一没一的为0
-----------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1111
==================================================================
//无符号右移4位
0000 0000 0000 0000 0000 0000 0000 0000
//或 有一的为一没一的为0
-----------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1111
==================================================================
//无符号右移8位
0000 0000 0000 0000 0000 0000 0000 0000
//或 有一的为一没一的为0
-----------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1111
==================================================================
//无符号右移16位
0000 0000 0000 0000 0000 0000 0000 0000
//异或 有一的为一没一的为0
-----------------------------------------------
0000 0000 0000 0000 0000 0000 0000 1111

发现什么规律没? 1.无符号右移最终的目的就是将n的二进制32位都变成0既:

0000 0000 0000 0000 0000 0000 0000 0000

不信我们可以吧java int类型的最大值 0x7fffffff按照上面的无符号右移

0x7fffffff = 0111 1111 1111 1111 1111 1111 1111 1111
0111 1111 1111 1111 1111 1111 1111 1111
0011 1111 1111 1111 1111 1111 1111 1111 >>> 1
0000 1111 1111 1111 1111 1111 1111 1111 >>> 2
0000 0000 1111 1111 1111 1111 1111 1111 >>> 4
0000 0000 0000 0000 1111 1111 1111 1111 >>> 8
0000 0000 0000 0000 0000 0000 0000 0000 >>> 16

发现没,移到16的时候就都变成了0,这里也可以说明为什么是16而不是8或者32,这个是因为java的int类型用4个字节32位来进行存储的。再往后也就没什么意义。而如果是小于16那么会出现移动不完的情况 2.异或的最终目的是将二进制的最高位的第一个1往后的0都变成1 同样拿int的最大值 0x7fffffff 做个实验

0111 1111 1111 1111 1111 1111 1111 1111
0011 1111 1111 1111 1111 1111 1111 1111 >>> 1
------------------------------------------------
0111 1111 1111 1111 1111 1111 1111 1111 | 
0000 1111 1111 1111 1111 1111 1111 1111 >>> 2
------------------------------------------------
0111 1111 1111 1111 1111 1111 1111 1111 | 
0000 0000 1111 1111 1111 1111 1111 1111 >>> 4
------------------------------------------------
0111 1111 1111 1111 1111 1111 1111 1111 | 
0000 0000 0000 0000 1111 1111 1111 1111 >>> 8
------------------------------------------------
0111 1111 1111 1111 1111 1111 1111 1111 | 
0000 0000 0000 0000 0000 0000 0000 0000 >>> 16
------------------------------------------------
0111 1111 1111 1111 1111 1111 1111 1111 | 

看见没亦或出来的一直都是它本身,说了这么多这一顿右移+或之后会得到什么结果呢? 来看看

传入1 = 1
传入2 = 2
传入3 = 4
传入4 = 4
传入5 = 8
...
传入8 = 8
传入9 = 16
...
传入16 = 16
传入17 = 32
...
传入32 = 32

也就是说上面这一端操作之后不管你传进来的是什么那么它都会算出比你传进来的值大的第一个二的次方减1 0、1、3、7、15、31、63、127、255...(注:这里说的是上面的运算,还没牵扯到第一行的+1和后面的(n+1)) 而后又做了

(n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

也就是当n小于0是返回1 当n大于等于int的最大值时 0x7fffffff 返回int的最大值否者返回n+1也就是2的n次方 1、2、4、8、16、32、64、128、256...也就是说不管你传进来的初始值是多少最终都会被HashMap计算出来的值取代(2的n次方)。 回过头分析这里为什么第一行会有 int n = cap - 1; 通过上面一系列说明我们可以发现计算的结果是算出比你传进来的值大的第一个二的次方减(注:上面的二的次方减-1和这里的不-1要区分,这里是最终的计算结果),那如果你传进来的数字正好是2的n次方比如 8 那么如果不减1那么最终算出的结果是

0000 0000 0000 0000 0000 0000 0000 1000
0000 0000 0000 0000 0000 0000 0000 0100 >>> 1
0000 0000 0000 0000 0000 0000 0000 1100 |
0000 0000 0000 0000 0000 0000 0000 0011 >>> 2
0000 0000 0000 0000 0000 0000 0000 1111 |
0000 0000 0000 0000 0000 0000 0000 0000 >>> 4
0000 0000 0000 0000 0000 0000 0000 1111 |
0000 0000 0000 0000 0000 0000 0000 0000 >>> 8
0000 0000 0000 0000 0000 0000 0000 1111 |
0000 0000 0000 0000 0000 0000 0000 0000 >>> 16
0000 0000 0000 0000 0000 0000 0000 1111 |

最终的结果是15再+1也就是16,也就是说你本来想创建一个容量为8的HashMap结果给你创建了一个16的容量,那么剩下的是不是就浪费了呢,所以需要先减去1再就是8-1=7,最终算出n=7再加1也就是8就正好对了,如果是9呢?9-1=8最后算出n=15+1 = 16 也就是会给你创建16个容量的容器 好了这个方法就说到这里,神奇吗? 算上{}这个方法也就9行的代码却需要这么一大堆的文字来分析与描述而且我这里还不一定讲的清楚。那这里为什么会使用2的指数幂作为容器的初始值我们下一次在容器扩容好数据拷贝的时候再来分析,以为在那里它将这写算法的运用说的凌厉尽致。也只有在使用的时候才会将其说清楚。