我的问题是,为什么哈希映射存储桶大小是2的幂,我在stackoverflow上经历了很多答案,但我仍然不相信。原因是:
我已经阅读到容量为2的幂使得计算索引的操作更加有效,因此我想知道在这里它到底有多有用。我的大小可能是3的幂,我仍然可以执行(hash)&(length-1)这样的&操作,所以为什么它确切应该是2的幂?
如果容量不是2的幂,为什么我需要进行余数运算?
答案 0 :(得分:4)
无论如何,您都需要执行余数运算以获取哈希码(可以是任何int
)以映射到哈希表中的条目。
在m
是2的幂且仅 的情况下,a % m
等于a & (m - 1)
。没有其他情况可以使用&
计算余数。
答案 1 :(得分:4)
当您从2的幂中减去1时,得到的是一个二进制表示形式均为1的数字。 16是2的幂。如果从中减去1,则得到15,其二进制表示形式是1111。现在,如果对1111进行任意数字的按位与运算,您将得到整数的最后4位。换句话说,它等于16的模数(除法运算通常是昂贵的运算。因此,按位运算通常比除法运算更可取)。最后4位将计算为从0到15的任何数字,这是基础数组的索引。
您可以改为17号。在这种情况下,从中减去1后,您将获得16,即10000的二进制值。现在,您要对数字与16进行位与运算,您将丢失数字的所有位(末尾第5位除外)。因此,无论您使用多少数字,数组索引都将为16或0。这意味着您将发生很多冲突,这又意味着性能很差。代替O(1)进行检索,您需要O(log n),因为发生冲突时,给定存储桶中的所有节点都将存储在红黑树中。不仅。如果您在多线程环境中使用ConcurrentHashMap
,则会遇到很多同步,因为所有新添加的内容最终只会存储在很少的存储桶中(在上述情况下,只有两个-0和16 ),然后在已经具有其他节点的存储桶中添加新节点时,该存储桶将被锁定,以避免由于多个线程进行修改而导致数据不一致。因此,其他尝试添加新节点的线程需要等待,直到当前线程释放锁为止。
最后,我还应该提到Java HashMap
实现也将键的哈希码向右移16位,并在执行与(length-1 ),以确保还捕获了高阶位的影响。
因此,基本上,要点是,如果大小是2的幂,则密钥将在阵列中更均匀地分布,而冲突最小,从而导致更好的检索性能(在ConcurrentHashMap
情况下,同步也较少) )与其他非2的幂的大小进行比较。
答案 2 :(得分:2)
我可以想到两个原因:
两者的幂使时间复杂性分析更加容易,因为在谈论计算时,log
通常被假定为基数2。(请注意,实际上,可以证明所有log
时间复杂度是等效的,无论其底数如何,但是如果您使用2的幂,则会使推理变得更容易,因为您的条件都是乘除以2)
两个的幂很好地匹配了硬件。将内存中的数字加倍要比将其乘以3少。同样,内存的大小都是2的幂,因此,如果您始终加倍,则总是会占用2 ^ n个 full 字节。