我创建了一个项目来对此类事情进行基准测试:http://code.google.com/p/hashingbench/ http://code.google.com/p/hashingbench/(对于具有链接、开放寻址和布隆过滤器的哈希表)。
除了 hashCode()的关键,你需要知道“涂抹”(或“加扰”,正如我在该项目中所说的那样)哈希表的函数。从这个清单 http://code.google.com/p/hashingbench/source/browse/trunk/src/hashing/Scramblers.java,HashMap的smearing函数相当于:
public int scramble(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
因此,对于 HashMap 中发生的冲突,必要 and 充足的条件如下:scramble(k1.hashCode()) == scramble(k2.hashCode())
. 这总是正确的,如果 k1.hashCode() == k2.hashCode()
(否则,涂抹/加扰功能将不会be一个函数),所以这是一个充足的, 但不是必要的碰撞发生的条件。
Edit:实际上,上述充分必要条件应该是compress(scramble(k1.hashCode())) == compress(scramble(k2.hashCode()))
- the compress
函数接受一个整数并将其映射到{0, ..., N-1}
, where N
是桶的数量,所以它基本上选择一个桶。通常,这可以简单地实现为hash % N
,或者当哈希表大小是 2 的幂时(这实际上是拥有 2 的幂的哈希表大小的动机),如hash & N
(快点)。 (“压缩”是 Goodrich 和 Tamassia 用于描述此步骤的名称,在Java 中的数据结构和算法 http://ww0.java4.datastructures.net/)。感谢 ILMTitan 发现了我的马虎行为。
其他哈希表实现(ConcurrentHashMap、IdentityHashMap 等)有其他需求并使用另一种涂抹/加扰函数,因此您需要知道您正在谈论哪一个。
(例如,HashMap 的涂抹功能之所以到位,是因为人们将 HashMap 与具有最差类型的 hashCode() 的对象一起使用,以实现没有涂抹的 HashMap 的旧的、二幂表实现 - 稍微不同的对象,或者根本不存在,在用于选择存储桶的低位位中 - 例如new Integer(1 * 1024)
, new Integer(2 * 1024)
*等。正如你所看到的,HashMap的smearing函数尽力了all bits影响低位)。
不过,所有这些都旨在在常见情况下正常工作 - 一个特殊情况是继承系统 hashCode() 的对象。
PS:实际上,促使实现者插入涂抹函数的绝对丑陋的情况是Floats / Doubles的hashCode(),以及作为值的键的用法:1.0,2.0,3.0,4.0 ...,它们都有相同的(零)低位。这是相关的旧错误报告:https://bugs.java.com/bugdatabase/view_bug?bug_id=4669519 https://bugs.java.com/bugdatabase/view_bug?bug_id=4669519