为什么HashMap会重新生成密钥对象提供的哈希码?

Var*_*rde 17 java collections hash hashmap hashcode

我正在阅读Java 1.6 API提供的HashMap类的代码,无法完全理解以下操作的需要(在put和get方法的主体中找到):

int hash = hash(key.hashCode());
Run Code Online (Sandbox Code Playgroud)

方法hash()具有以下主体:

 private static int hash(int h) {
         h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
Run Code Online (Sandbox Code Playgroud)

这通过对提供的哈希码执行位操作来有效地重新计算哈希值.即使API声明如下,我也无法理解这样做的必要性:

这很关键,因为HashMap使用两个幂的长度哈希表,否则会遇到低位不同的hashCodes的冲突.

我确实理解键值是存储在数据结构数组中的,并且该数组中项的索引位置由其哈希确定.我无法理解的是这个函数如何为哈希分布添加任何值.

tuc*_*uxi 26

正如Helper写的那样,只是在关键对象的现有哈希函数出错并且没有做好混合低位的足够好的工作的情况下.根据pgras引用的消息来源,

 /**
  * Returns index for hash code h.
  */
 static int indexFor(int h, int length) {
     return h & (length-1);
 }
Run Code Online (Sandbox Code Playgroud)

哈希与两个幂的长度进行AND运算(因此,length-1保证是1的序列).由于此ANDing,仅使用较低位h.其余的h被忽略了.想象一下,无论出于何种原因,原始哈希仅返回可被2整除的数字.如果直接使用它,则不会使用哈希图的奇数位置,从而导致冲突数量增加x2.在真正的病态情况下,错误的散列函数可以使散列图的行为更像列表,而不是像O(1)容器.

Sun工程师必须运行测试,这些测试表明太多的哈希函数在其较低位中不够随机,并且许多哈希映射不够大,无法使用较高位.在这些情况下,hash(int h)即使需要额外的计算,HashMap中的位操作也可以提供大多数预期用例的净改进(由于较低的冲突率).

  • "以防万一"?实际上,Java中的大多数哈希代码都会变得糟糕.例如,请查看java.lang.Integer!但这实际上是有道理的.最好说"如果每个人的Object.hashCode()都有糟​​糕的比特分布,只要它们遵循等于对象有等于哈希码的规则,并尽可能地避免冲突,那就没关系." 然后,只有像HashMap这样的集合实现会承担通过辅助散列函数传递这些值的负担,而不是每个人的问题. (3认同)
  • 好吧,想象一下我正在哈希Employee对象,并且我的所有员工都有一个int ID字段,例如"400114","400214","400314"等等(它们都共享其ID的"14"部分,因为它是我部门的后缀).Integer的hashCode()方法返回整数本身 - 所以如果我在HashSet /没有/ HashMap的哈希(int h)中使用employee-IDs作为键,则传播将非常非常不均匀.在这个例子中,由于14是偶数,因此只能使用偶数桶. (2认同)