我正在阅读Joshua Bloch的 Effective Java 的Chapter 3。在项目8:覆盖等于时始终覆盖hashCode,作者在其散列函数中使用以下组合步骤:
result = 37 * result + c;
然后他解释了为什么选择37(强调添加):
选择乘法器37是因为它是奇数素数。 如果它是偶然的 乘法溢出,信息会因为乘法而丢失 两个相当于移位。使用素数的优点较少 很清楚,但为此目的使用素数是传统的。
我的问题是为什么组合因子(37)奇数很重要?不管乘数是奇数还是偶数,乘法溢出都不会导致信息丢失吗?
答案 0 :(得分:15)
考虑当一个正值在一个base-2表示中重复乘以2时会发生什么 - 所有的设置位最终都会从结尾开始,让你为零。
偶数乘数会导致哈希码具有较少的多样性。
另一方面,奇数可能会导致溢出,但不会损失多样性。
答案 1 :(得分:4)
hashCode的目的是根据输入获得随机位(特别是低位,因为这些位通常使用更多)
当你乘以2时,最低位只能是0,缺少随机性。如果乘以奇数,则最低位可以是奇数或偶数。
类似的问题是你在这里得到什么
public static void main(String... args) {
System.out.println(factorial(66));
}
public static long factorial(int n) {
long product = 1;
for (; n > 1; n--)
product *= n;
return product;
}
打印
0
每隔一个数字是偶数,每四分之一是4等的倍数。
答案 2 :(得分:2)
解决方案在于数论和乘数的Lowest common denominator以及你的模数。
一个例子可能有所帮助。让我们说,而不是32位你只有2位代表一个数字。所以你有4个数字(类)。 0,1,2和3
CPU中的溢出与模运算相同
Class - x2 - mod 4 - x2 - mod 4
0 0 0 0 0
1 2 2 4 0
2 4 0 0 0
3 6 2 4 0
在2次操作之后,你只剩下1个可能的数字(类)。所以你“丢失”了信息。
Class - x3 - mod 4 - x3 - mod 4 ...
0 0 0 0 0
1 3 3 9 1
2 6 2 6 2
3 9 1 3 3
这可以永远持续,你仍然拥有所有4个班级。所以你不要丢失信息。
关键是,你的muliplier和你的modulo类的LCD是1.这适用于所有奇数,因为你的模数目前总是2的幂。他们不必是素数而他们没有必要特别是37。但信息丢失只是选择37的标准,其他标准是价值分配等。
答案 3 :(得分:0)
非数学简单版为什么......
素数用于散列以保持多样性。
由于Set和Map实现,也许多样性更重要。这些实现使用对象哈希数的最后一位来索引内部条目数组。
例如,在带有内部表(数组)的HashMap中,对于大小为8的条目,它将使用最后3位哈希数来对表项进行地址。
static int indexFor(int h, int length) { return h & (length-1); }
实际上它不是,但如果Integer对象有
hash = 4 * number;
大多数表格元素都是空的,但有些会包含太多条目。这将导致在搜索特定条目时进行额外的迭代和比较操作。
我想Joshua Bloch的主要关注点是尽可能地分配散列整数,以通过在Maps和Sets中均匀分布对象来优化集合的性能。直觉上的素数似乎是一个很好的分配因素。
答案 4 :(得分:0)
素数不是确保多样性所必需的;必要的是因子是模数的相对质数。
由于二进制算术的模数总是2的幂,所以任何奇数都是相对素数,并且就足够了。但是,如果您采用溢出以外的模数,则素数将继续确保多样性(假设您没有选择相同的素数......)。