Dictionary<TKey, TValue> 中的散列过程是如何工作的

Posted

技术标签:

【中文标题】Dictionary<TKey, TValue> 中的散列过程是如何工作的【英文标题】:How does the process of hashing work in Dictionary<TKey, TValue> 【发布时间】:2010-11-27 07:29:39 【问题描述】:

字典中的散列过程是如何工作的?我读到使用字典可以更快地查找。但不明白怎么办?哈希和映射到索引是如何发生的?找不到任何好的参考。

编辑: 对象存储的实际内存位置是如何从哈希函数的结果中得到的?

【问题讨论】:

见how-does-a-hash-table-work 【参考方案1】:

哈希表或字典是一种存储键值对的数据结构。哈希表的优点是给定一个键找到相应的值非常快。简而言之,在哈希表中找到键值对的时间不取决于表的大小。将其与将键值对存储在列表或数组中进行比较。要查找键值对,您必须从头开始搜索列表,直到找到匹配的键。列表越长,查找键值对所需的时间就越多。使用 big-O 表示法,您可以说在哈希表中查找键的顺序为 O(1),而使用线性搜索在列表中查找键的顺序为 O(N)(简化)。

要在哈希表中插入键值对,您首先必须计算键的哈希码。在 .NET 中,所有对象都有一个名为 GetHashCode 的方法,该方法返回该特定对象的哈希码(32 位整数)。相等的对象返回相同的哈希码很重要,但如果不同的对象返回不同的哈希码也非常有用。请注意不同对象不能返回相同哈希码的误解——它们可以,但会导致冲突(见下文)。

以两个字符串的哈希码为例:

“嘘” 0x598FD95A "Foo" 0x598FD8DE

尽管字符串非常相似,但它们具有不同的哈希码。

我在这里稍微简化一下,以专注于哈希表的重要方面,所以现在让我们说 Dictionary&lt;TKey, TValue&gt; 在内部将键值对存储在数组中。要在此数组中找到将存储键值对的索引,您必须以数组大小为模来计算键的哈希码。假设数组的大小为 5:

索引(“嘘”)= 0x598FD95A % 5 = 4 索引(“Foo”)= 0x598FD8DE % 5 = 0

这导致了这个内部哈希表数组:

+---+----------+ | 0 | “福” | +---+----------+ | 1 | (空) | +---+----------+ | 2 | (空) | +---+----------+ | 3 | (空) | +---+----------+ | 4 | “嘘” | +---+----------+

在哈希表中查找条目非常快。您只需计算键的哈希码,以内部数组的大小为模,然后检索该索引处的字符串。

现在考虑关键“动物园”:

索引(“动物园”)= 0x598FDC62 % 5 = 0

它与键“Foo”具有相同的索引。这会导致所谓的冲突。哈希表的正确实现必须处理冲突,并且有different strategies for doing that。此外,随着内部数组被填满,数组中的空元素将越来越少,从而导致冲突数量增加。 负载因子是内部数组中已使用元素与总元素之间的比率。在上面的示例中,负载因子为 2/5 = 0.4。当负载因子超过某个阈值时,大多数哈希表实现都会增加内部数组的大小。

如果您想了解更多关于其中一些概念的信息,则必须研究其他答案中链接的一些更全面的资源。

【讨论】:

你应该是一名老师 :) 但是我仍然不明白一件事 - 数组的大小可能会改变是有道理的,当做'key modulo the size of数组'? @BornToCode:我的回答只解释了哈希表的基本概念,但Wikipedia article 有更多细节。回答您的问题:通常,当调整数组大小时,会创建一个新的空数组,并通过计算以新大小为模的哈希值将所有条目从旧表复制到新数组中的新位置。【参考方案2】:

字典使用散列键进行查找,正如我在my answer to your other question 中解释的那样。因此,如果您有一个自定义对象类型作为键,那么一切都取决于您的自定义对象的 GetHashCode() 实现。

【讨论】:

【参考方案3】:

字典中的散列过程使用一种称为链接的技术。 通过链接,使用辅助数据结构来保存任何冲突。具体来说,字典中的每个插槽都有一个映射到存储桶的元素数组。在发生碰撞时,碰撞元素会被添加到存储桶列表中。

有关详细信息,请参阅 MSDN 上的 this 文章。

【讨论】:

【参考方案4】:

通常情况下,通过取散列值%数组大小,可能会产生冲突。

【讨论】:

【参考方案5】:

通过使用称为Hash Map 的计算机科学概念。这确实比搜索列表更快。这通过使搜索不需要遍历列表直到找到匹配项来起作用。相反,键是“hashed”,并用作列表的索引。这个散列函数几乎总是比搜索列表(通过多重比较迭代)更快。

【讨论】:

如何从哈希函数的结果中得到对象实际存放的内存位置? @novice:阅读***页面。

以上是关于Dictionary<TKey, TValue> 中的散列过程是如何工作的的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Dictionary<TKey, TValue> 上的这个 Linq 查询不能作为数据源工作

Dictionary<TKey, TValue> 的线程安全

我可以为 Dictionary<TKey, TValue> 条目使用集合初始化程序吗?

Dictionary<TKey, TValue> 中的散列过程是如何工作的

Lookup<TKey, TElement> 的意义何在?

ConcurrentDictionary与Dictionary