我正在阅读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的冲突 低位。
我确实理解键值pars存储在数据结构数组中,并且此数组中项的索引位置由其哈希确定。 我无法理解的是这个函数如何为哈希分布添加任何值。
答案 0 :(得分:25)
正如Helper写的那样,只是在关键对象的现有哈希函数出错并且没有做好混合低位的足够好的工作的情况下。根据pgras引用的the source,
/**
* 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)
中的位操作可以提供大多数预期用例的净改进(由于较低的冲突率),即使需要额外的计算。
答案 1 :(得分:2)
我在某处读到这是为了确保良好的分发,即使你的hashCode实现,嗯,错误,糟透了。
答案 2 :(得分:2)
如您所知,使用hashmap,底层实现是一个哈希表,特别是一个封闭桶哈希表。负载系数确定集合中的适当对象数量/存储桶总数。
让我们假设您不断添加更多元素。每次这样做,并且它不是更新时,它运行对象的hashcode方法,并使用带模数运算符的桶数决定对象应该进入哪个桶。
随着n(集合中的元素数量)/ m(桶数)变大,读写性能变得越来越差。
假设您的哈希码算法很惊人,性能仍然取决于此比较n / m。
rehashing也用于更改存储桶的数量,并且仍然保持与构建集合相同的加载因子。
请记住,任何哈希实现的主要好处是读写的理想O(1)性能。
答案 3 :(得分: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的每一点都离它自己太远了。所以这将是非常随机的,不会挤满任何特定的桶。希望这有帮助。如果您需要完整的视觉效果,请给我发送电子邮件。