当 HashMap<String, Integer> map = new HashMap<>(16, 0.75f); 这个16改成3可以吗?
答案:
非 2 的幂次方: 当容量不是 2 的幂次方时,capacity - 1
的二进制表示将包含多个 1,这使得位掩码操作(hash & (capacity - 1)
)不能简单地映射到桶的索引。
- 例子:
- 假设
capacity
是 6(即00000110
),capacity - 1
是 5(即00000101
)。 - 如果
hash
是101010
,则hash & (capacity - 1)
计算为101010 & 00000101
,结果是00000000
,即 0。 - 这可能会导致索引分布不均匀,因为计算结果可能没有准确地反映哈希值的实际位置。
- 假设
为什么不均匀呢?
答案:
当 HashMap
的容量不是 2 的幂次方时,计算桶索引的操作效率会降低,主要是因为位掩码操作(hash & (capacity - 1)
)不再适用。以下是详细的解释:
位掩码操作的工作原理
1. 位掩码操作
当容量为 2 的幂次方时(例如 16、32、64 等),计算桶的索引可以通过位掩码操作来高效完成。这是因为:
-
位运算: 位掩码操作利用了二进制位运算的特性,具体为
index = hash & (capacity - 1)
。capacity
是桶的数量,通常是 2 的幂次方。capacity - 1
: 例如,如果capacity
是 16,那么capacity - 1
是 15(二进制表示为1111
)。
位掩码操作等效于取模运算,但通常更高效,因为它只涉及按位与运算。
- 例子:
- 假设
hash
是101010
(二进制表示)。 - 如果
capacity
是 16(即00010000
),那么capacity - 1
是 15(即00001111
)。 - 计算桶索引为
101010 & 00001111
,结果为00001010
,即 10。
- 假设
2. 为什么 2 的幂次方的容量使位掩码操作更高效
-
二进制特性: 当容量是 2 的幂次方时,二进制表示中只有一位是 1,其余为 0。
capacity - 1
将所有低位变为 1。这使得位与操作(hash & (capacity - 1)
) 能够迅速得到桶的索引。 -
模运算的代价: 位掩码操作比取模运算更高效,因为取模运算涉及除法,而除法在计算机中通常比位运算慢得多。取模运算需要计算精确的商和余数,而位掩码只涉及简单的按位操作。
当容量不是 2 的幂次方时
1. 计算桶索引的复杂性
-
模运算: 如果桶的容量不是 2 的幂次方,例如 6,那么计算桶的索引需要使用模运算:
index=hash%6\text{index} = \text{hash} \% 6index=hash%6
- 这个操作涉及除法,计算上比位运算复杂得多。
- 除法操作在硬件中比位运算慢,这会增加计算的时间和开销。
2. 位掩码操作不适用
-
非 2 的幂次方: 当容量不是 2 的幂次方时,
capacity - 1
的二进制表示将包含多个 1,这使得位掩码操作(hash & (capacity - 1)
)不能简单地映射到桶的索引。- 例子:
- 假设
capacity
是 6(即00000110
),capacity - 1
是 5(即00000101
)。 - 如果
hash
是101010
,则hash & (capacity - 1)
计算为101010 & 00000101
,结果是00000000
,即 0。 - 这可能会导致索引分布不均匀,因为计算结果可能没有准确地反映哈希值的实际位置。
- 假设
- 例子:
3. 性能影响
-
哈希冲突: 使用非 2 的幂次方容量可能导致桶的分布不均匀,从而增加哈希冲突。哈希值的分布在这种情况下可能更加集中,导致多个键映射到相同的桶中。
-
扩容和重散列: 如果初始容量不是 2 的幂次方,那么扩容后也不一定是 2 的幂次方。扩容过程中的重新分配和计算可能需要额外的开销,影响性能。
总结
-
2 的幂次方的容量: 允许使用位掩码操作(
hash & (capacity - 1)
),这是一种高效的计算桶索引的方法。它避免了除法运算的开销,提高了性能。 -
非 2 的幂次方的容量: 需要使用模运算来计算桶索引,模运算涉及除法,计算上较慢,可能导致性能下降。
因此,使用 2 的幂次方作为哈希表的容量可以提高 HashMap
的性能和效率,这也是为什么建议在初始化 HashMap
时使用 2 的幂次方作为初始容量。