文章目录:①、抛题②、给出结论③、论证题④、&和%运行效率对比相信对JDK源码感兴趣的朋友,一定不要错过HashMap的源码,里面有里面有很多精妙的设计,也是面试常见的考点。我将在本文中指出一些。不过HashMap的源码我就不详细介绍了。如果想了解更多,可以看我之前的文章。本文主要是为大家解决一些问题。1、问题1.1为什么HashMap默认初始容量长度为1<<4?为什么源码这里不直接写16呢?写1<<4?知道的可以在评论区留言。1.2为什么HashMap初始化时给定了长度还需要重新计算一个2^n^的数?PS:该方法用于计算HashMap的初始容量,结果返回第一个大于给定值的偶数,如:newHashMap(10),实际长度为16;newHashMap(18),实际长度为32;newHashMap(40),实际长度为64,涉及两个操作符:①,|:或运算符。②、>>>:无符号右移运算符,移动时忽略符号位,空位补0。这个算法也挺有意思的,原理是每次运算把已有的0位都变成1,直到所有位为1;最后返回结果的时候,如果小于最大值,则返回结果+1,只是把所有的1都变成0,再加一位,正好是2^n^。1.3为什么HashMap每次扩容都会翻倍,即2^1^?当HashMap存储的元素占整个容量的75%以上时(默认加载因子DEFAULT_LOAD_FACTOR=0.75),扩容,在不超过int指定类型范围时,左移1位(扩充为原长度的两倍)1.4为什么HashMap的索引计算算法是&而不是%?当添加新元素时,会计算该元素在HashMap中的位置。算法分为两步:①获取hash值PS:其实这里的算法可以研究下,hashcode()是native修饰的方法,返回一个int类型的值(根据内存地址转换的值),然后将这个值无符号右移16位,高位补0,与上一步得到的哈希码进行按位异或^运算。这样做有什么用?这其实是一个扰动函数,为了减少哈希码的碰撞。右移16位,刚好是32位的一半。将高低半区进行异或运算,将原哈希码的高低位混合,增加低位的随机性。这样,混合的低位掺杂了高位的一些特征,高位的信息也变相的保留了下来。即保证高位和低位都参与Hash的计算。②.Indexvaluei=(n-1)&hash其中n为HashMap的长度,hash为上一步得到的值。注意:这里使用的是按位与&,不是取模%。1.5问题总结有没有发现,通过我上面提的四个问题,前三个问题HashMap的长度一直保持在2^n^。①、默认初始长度为2^4^;②、即使给定初始长度,其值仍然是第一个大于给定值的偶数;③、每展开一倍,2^1^;那么第四题,在计算HashMap的元素索引的时候,我们得到的是一个hash值,但是它实际上是对HashMap的长度进行了&操作,而不是%操作。为什么?2.给出结论我们直接给出结论:当lenth=2^n^且X>0时,X%length=X&(length-1)即当length为2的n次方时,modulus操作%可以转化为按位与&操作。例如:9%4=1,9的二进制为1001,4-1=3,3的二进制为0011。9&3=1001&0011=0001=1又如:12%8=4,12的二进制是1100,8-1=7,7的二进制是011112&7=1100&0111=0100=4上面两个例子4和8都是2的n次方,结论是有效,那么如果长度不是2的n次方呢?例如:9%5=4,9二进制为1001,5-1=4,4二进制为0100。9&4=1001&0100=0000=0。显然不成立。为什么会这样?下面我们详细分析一下。3.论证结论首先,我们需要知道以下规则:①、“<<”左移:将所有数以二进制形式向左移动相应的位数,去掉高位(舍弃),并用零填充低位。左移一位相当于乘以2。②、“>>”右移:将所有数向右移动二进制形式对应的数位。对于左边移出的空格,如果是正数,空格会补0,如果是负数,可能补0或1,这取决于使用的计算机系统。右移一位相当于除以2。③、“>>>”无符号右移:挤掉右边的位,左边移出的空位全部补0。根据二进制数的特点,相信大家都能很好的理解。现在给定一个任意十进制数X~n~X~n-1~X~n-2~....X~1~X~0~,我们将其分解为二进制表示:公式1:X~n~X~n-1~X~n-2~....X~1~X~0~=X~n~2^n^+X~n-1~2^n-1^+......+X~1~2^1^+X~0~2^0^回到上面的结论:当lenth=2^n^andX>0时,X%length=X&(length-1)and对于除法,除以一个数可以用除法分配律,几个数的和或差不能用除法分配律:公式2:成立:(a+b)÷c=a÷c+b÷c不成立:a÷(b+c)≠a÷c+a÷c通过公式1和公式2,我们可以得到当任意小数除以2^k^的数时,我们可以把这个小数转换成公式1的表示形式:如果我们要求上面公式的余数,相信大家一眼就能看出来:①。当0<=k<=n时,余数为X~k~2^k^+X~k-1~2^k-1^+......+X~1~2^1^+X~0~2^0^,即n大于k的次方,我们全部舍弃(大的能整除2^k^),小于k的保留(小的不能整除2^k^).然后留作余数。②.当k>n时,余数为整数。看到这里,我们离证明结论已经很近了。回到上面提到的二进制移位运算,右移n位就是除以2^n^的次方,从中我们得到一个很重要的结论:一个十进制数等于一个2^n^数另外,我们可以把这个十进制数转换成二进制数,把二进制数右移n位,去掉的n位就是余数。既然我们知道了如何计算余数,那么我们如何得到去除的n个数呢?我们来看二进制的2^0^,2^1^,2^2^...2^n^如下:0001,0010,0100,1000,10000...我们把上面的数减一:0000,0001,0011,0111,01111...根据AND运算符&的规律,当所有位都为1时,结果为1,否则为0。所以当任意二进制数修改2^k^时,我们可以将这个二进制数与(2^k^-1)进行按位与运算,剩下的就是余数。这完美的证明了上面给出的结论:当lenth=2^n^andX>0时,X%length=X&(length-1)4,&and%operationefficiency为什么要把%运算改成&运算是明明是因为&运算只需要计算机底层一个简单的与逻辑门,但实现除法确实是一个非常复杂的电路。如果你有兴趣,你可以继续研究它。我这里在代码层面做一个测试,让大家直观比较速度:publicclassHashMapTest{publicstaticvoidmain(String[]args){longnum=100000*100000;longstartTimeMillis1=System.currentTimeMillis();长结果=1;for(longi=1;i
