Java hashCode、实现以及与HashMap的关系
所以我问了另一个相关的问题:,但我现在有一个不同的相关问题 我在那个问题中确定的是,String的hashCode()函数没有雪崩效应。这意味着,例如,如果我有字符串“k1”、“k2”、“k3”,并且我对每个字符串调用hashCode(),则返回的值将是连续的 现在,根据我对数据结构101的记忆,我觉得这是件坏事。因为假设HashMap通过如下算法选择存储桶:Java hashCode、实现以及与HashMap的关系,java,hashmap,hashcode,Java,Hashmap,Hashcode,所以我问了另一个相关的问题:,但我现在有一个不同的相关问题 我在那个问题中确定的是,String的hashCode()函数没有雪崩效应。这意味着,例如,如果我有字符串“k1”、“k2”、“k3”,并且我对每个字符串调用hashCode(),则返回的值将是连续的 现在,根据我对数据结构101的记忆,我觉得这是件坏事。因为假设HashMap通过如下算法选择存储桶: class HashMap { private int capacity; private int chooseBuck
class HashMap {
private int capacity;
private int chooseBucket(String key) {
return key.hashCode() % capacity;
}
}
这意味着相似的密钥存储在相邻的存储桶中,导致更高的冲突率,将大O查找时间从O(1)降低到…谁知道有多糟…可能比O(logn)更糟 对于我的第一个问题,我得到的答案大致是“这里不需要雪崩效应”、“它只用于加密哈希函数”和“字符串的哈希代码实现速度快,适用于小哈希映射” 这让我很困惑。当数据结构很小时,它们都很快。Sun不是提供了一个默认的hashCode函数,可以很好地用于大型数据集吗?这就是HashMap的性能真正重要的时候了,不是吗
或者,我错过了什么?请告诉我。在连续存储桶中存储密钥不会导致性能下降。将钥匙存储在同一个存储桶(例如)中不起作用。使用链接解决哈希冲突时:
- 最坏的情况是:每个散列值都相同,因此所有元素都在同一个bucket中,在这种情况下,您将获得O(n)性能(假设链是链表)
- 最佳情况:每个散列值都是不同的,因此每个元素都位于不同的存储桶中,因此您可以获得预期的O(1)性能
散列表(及类似的)中使用的散列码不需要一个散列函数。散列图之类的散列函数对于它的键集需要合理地唯一,但键之间的关系(即两个键的相似程度)不需要是随机的。我们真正想要避免的是在一个桶中有一堆对象,这会使搜索该桶变得昂贵 在HashMaps和string的情况下,它必须将这些散列键映射到一个随机可访问的容器中,比如一个数组,该数组有许多解决方案,但是如果两个键“接近”,它仍然会导致它们被放在不同的桶中,这正是我们真正关心的 对于非常大的映射容器(想想数十亿个键),我们可能确实希望变得更聪明一点,但这似乎超出了Java HashMap的设计目的
最后一点,您不必使用雪崩效应为字符串生成相当随机的键。你想选择一个足够随机且尽可能快的函数。前几天我读了Eric Lippert的一篇博客文章,标题是。尽管代码示例与C#相关,但大多数一般原则同样适用于Java。如果您想更多地了解哈希代码的用途以及如何生成它们,那么本文非常值得一读 特别是,以下内容似乎与您的问题特别相关: 指南:散列码的分布必须是“随机的” 所谓“随机分布”,我的意思是,如果被散列的对象中存在共性,那么在生成的散列代码中就不应该存在类似的共性
如果您查看HashMap的源代码,就会发现有一个使用key.hashCode()值调用的hash函数,这意味着它会按照自己的方式分配一个hash。需要确定的一点是不要遵守equals和hashcode契约。如果您希望提高性能,我建议您查看源代码,了解可用存储桶的数量和最佳使用情况。您会问“或者,我遗漏了什么吗?请告诉我。” 是的,你错过了一些东西 在HashMap类实现中,它可以防止糟糕的哈希函数:
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
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);
}
因此,在您的示例中生成的哈希代码是:
k1 - Before: 3366 After: 3566
k2 - Before: 3367 After: 3567
k3 - Before: 3368 After: 3552
因此,即使在你的3个元素的小样本中,其中一个元素被重新灰化了。现在,这并不能防止恶意的哈希代码(return randomInt();
或return 4;
根本无法防止),但它确实可以防止写得不好的哈希代码
我还应该指出,通过使用非平凡的输入,您可以极大地改变事情。例如,考虑以下字符串。
k1longer - Before: 1237990607 After: 1304548342
k2longer - Before: 2125494288 After: 2040627866
k3longer - Before: -1281969327 After: -1178377711
注意低位有多大的不同:对于哈希代码来说,唯一重要的是低位。背景图的大小始终是2的幂。实际上,代码中是这样记录的:
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
重新灰化在确保高位(在哈希表中通常被忽略)仍然对低位有影响方面做得相当不错。以下是原始哈希代码位置及其影响的位的映射:
00: 00000000000000000000000000000001
01: 00000000000000000000000000000010
02: 00000000000000000000000000000100
03: 00000000000000000000000000001000
04: 00000000000000000000000000010001
05: 00000000000000000000000000100010
06: 00000000000000000000000001000100
07: 00000000000000000000000010001001
08: 00000000000000000000000100010010
09: 00000000000000000000001000100100
10: 00000000000000000000010001001000
11: 00000000000000000000100010010000
12: 00000000000000000001000100100001
13: 00000000000000000010001001000010
14: 00000000000000000100010010000100
15: 00000000000000001000100100001000
16: 00000000000000010001001000010001
17: 00000000000000100010010000100010
18: 00000000000001000100100001000100
19: 00000000000010001001000010001001
20: 00000000000100010010000100010011
21: 00000000001000100100001000100110
22: 00000000010001001000010001001100
23: 00000000100010010000100010011000 # means a 1 in the 23rd bit position will
24: 00000001000100100001000100110001 # cause positions 4, 5, 8, 12, and 20 to
25: 00000010001001000010001001100010 # also be altered
26: 00000100010010000100010011000100
27: 00001000100100001000100110001001
28: 00010001001000010001001100010010
29: 00100010010000100010011000100100
30: 01000100100001000100110001001000
31: 10001001000010001001100010010000
因此,您对“将大O查找时间从O(1)降低到…谁知道有多糟…可能比O(logn)更糟”和“Sun是否会提供一个适用于大型数据集的默认hashCode函数?”的担忧可能会得到缓解-他们有适当的保护措施来防止这种情况发生
如果这有助于你获得一些平静,下面是这个类的作者标签。他们实际上是爪哇世界的明星。(带#的评论是我的)
“相似的密钥存储在相邻的存储桶中,导致更高的冲突率”——为什么这会导致更多的冲突?我不认为这句话有助于回答这个问题。在我看来,它听起来确实希望哈希代码中出现雪崩效应,这对于哈希表来说是不必要的。我有点同意
* @author Doug Lea # Formerly a Java Community Process Executive Committee member
* @author Josh Bloch # Chief Java architect at Google, amongst other things
* @author Arthur van Hoff # Done too many hardcore Java things to list...
* @author Neal Gafter # Now a lead on the C# team at Microsoft, used to be team lead on javac