现象
在HashMap源码中,Size字段是这么存储的:
一个很有意思的现象是,为什么这里16不是直接用的16,而是1 << 4
。
在StatckOverFlow 中有这么一篇回答,可以深刻阐述背后的原因。总结一句话就是计算机是二进制的世界。
原文地址:Why use 1<<4 instead of 16?
先说上面为什么用 1<<4,这里这么写,并不会改变背后的影响,这么写的一个原因是告诉开发者,容量必须是2的倍数。同时暗示开发者,容量在存储为二进制时,永远只有一位是1。比如上面的0001 0000。所以源码中描述最大容量用的也是 1 << 30。显然,用移位的表示方法更具可读性。
为什么必须是2的倍数?
在进行Hash运算的时候,Java底层是取余运算,比如,如果容量是100,那么Hash值为5、105、205的键值对都将存储在index为5的地方。这里的思想就像钟表一样是个环形的。
这里就涉及到了取余,想象一下,如果容量随意,比如1234,那么当Hash值是123456的时候,我们将进行,123455 % 1234的运算,让计算机计算看上去还行。
但如果我们不随意指定容量,而是指定容量必须是10的倍数,将会发生什么呢?比如容量是100,那么当Hash值是123456的时候,我们一眼就能看出 123456 % 100 = 56,所以应该存储在index是56的地方。
Amazing啊。
那对于计算机,有没有一眼就能求出来的设计方案呢?答案是容量指定为2的倍数,比如上图中,默认初始容量是16,那么底层存储的二进制码将是 10000
,我们随机取一个Hash值用二进制表示,比如10111010110
,那么怎么求得 10111010110 % 10000
的结果呢?答案是 (10000 - 1) & 10111010110 = 0110
。Amazing啊!!!一下就求出来了。
虽然现在计算机的运算能力很强,但是形如上面的123456 % 100 = 56
的运算速度依然要比形如(10000 - 1) & 10111010110 = 0110
的位运算速度小很多,在操作量比较大的情况下,采用后者的设计将带来非常可观的性能提升!!!甚至超过50倍!!!!!
在回到前面提到的计算机是二进制的世界,试想一下,如果计算机是基于三进制的,那可能HashMap的容量要求可能就不是2的倍数,而是三的倍数了。:)
共勉
所有你不能理解的计算机现象,都能从下一层设计中找到答案。