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

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

int hash = hash(key.hashCode()); 

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

  private static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 

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

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

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

正如Helper写的那样,它就是为了防止关键对象的现有哈希函数出错并且没有做好混合低位的工作。 根据pgras引用的消息来源 ,

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

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

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

我在某处读到这是为了确保良好的分发,即使你的hashCode实现,好吧,错误,糟透了。

如您所知,使用hashmap,底层实现是一个哈希表,特别是一个封闭桶哈希表。 负载因子确定集合中的适当对象数量/存储桶总数。

让我们假设您不断添加更多元素。 每次这样做,并且它不是更新,它运行对象的hashcode方法,并使用模数运算符的桶数来决定对象应该进入哪个桶。

随着n(集合中的元素数量)/ m(桶的数量)变大,读写性能变得越来越差。

假设您的哈希码算法很惊人,性能仍然取决于这种比较n / m。

rehashing也用于更改存储桶的数量,并且仍然保持与构建集合相同的负载因子。

请记住,任何哈希实现的主要好处是读取和写入的理想O(1)性能。

如您所知,object.hashCode()可以被用户覆盖,因此非常糟糕的实现会抛出非随机的低级别位。 这往往会挤掉一些桶,并会留下许多桶。

我刚刚创建了一个关于他们在哈希中尝试做什么的可视化地图。 看起来hash(int h)方法只是通过进行位级别的创建来创建一个随机数,这样得到的数字就更随机(因此更均匀地分配到桶中)。

每个位都重新映射到不同的位,如下所示:

  h1 = h1 ^ h13 ^ h21 ^ h9 ^ h6 h2 = h2 ^ h14 ^ h22 ^ h10 ^ h7 h3 = h3 ^ h15 ^ h23 ^ h11 ^ h8 h4 = h4 ^ h16 ^ h24 ^ h12 ^ h9 h5 = h5 ^ h17 ^ h25 ^ h13 ^ h10 

。 。 。 。

到12点。

正如你所看到的,h的每一点都离它自己太远了。 所以这将是非常随机的,不会挤满任何特定的桶。 希望这有帮助。 如果您需要完整的视觉效果,请给我发