有一个常见的误解,即如果您具有唯一的hashCode(),则不会发生冲突。 虽然唯一或几乎唯一的hashCodes很好,但这还不是故事的结局。
问题在于HashMap的大小不是无限的(或大小至少为2 ^ 32),这意味着hashCode()的数量必须减少为较小的位数。
HashMap以及HashSet和LinkedHashMap的工作方式是按以下方式对位进行突变:
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
然后为最低位应用掩码以选择存储区。 问题在于,即使像Integer一样使用唯一的hashCode(),也会有具有不同哈希码的值映射到同一存储桶。 您可以研究Integer.hashCode()的工作方式:
public static void main(String[] args) {Set integers = new HashSet<>();for (int i = 0; i <= 400; i++)if ((hash(i) & 0x1f) == 0)integers.add(i);Set integers2 = new HashSet<>();for (int i = 400; i >= 0; i--)if ((hash(i) & 0x1f) == 0)integers2.add(i);System.out.println(integers);System.out.println(integers2);}static int hash(int h) {// This function ensures that hashCodes that differ only by// constant multiples at each bit position have a bounded// number of collisions (approximately 8 at default load factor).h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
}
打印:
[373, 343, 305, 275, 239, 205, 171, 137, 102, 68, 34, 0]
[0, 34, 68, 102, 137, 171, 205, 239, 275, 305, 343, 373]
条目的顺序与之相反,因为它们是作为HashMap的链接列表而添加的,将所有条目置于同一存储桶中。
解决方案?
一个简单的解决方案是让一个存储桶变成一棵树,而不是一个链表。 在Java 8中,它将对字符串键执行此操作,但是可以对所有可比较类型AFAIK执行此操作。
另一种方法是允许使用自定义哈希策略,以使开发人员避免此类问题,或者在每个集合的基础上随机化突变,从而将应用程序的成本摊销到应用程序中。
其他注意事项
我倾向于支持64位哈希码,尤其是支持复杂对象的哈希码。 哈希码本身发生冲突的可能性很小,并且很好地支持非常大的数据结构。 例如数十亿。
翻译自: https://www.javacodegeeks.com/2013/10/unique-hashcodes-is-not-enough-to-avoid-collisions.html