为啥 .Net 字典中的条目要另外排序?

Posted

技术标签:

【中文标题】为啥 .Net 字典中的条目要另外排序?【英文标题】:Why are entries in addition order in a .Net Dictionary?为什么 .Net 字典中的条目要另外排序? 【发布时间】:2010-09-14 08:02:54 【问题描述】:

我刚刚看到这种行为,我有点惊讶......

如果我在字典中添加 3 或 4 个元素,然后执行“For Each”来获取所有键,它们会按照我添加它们的顺序出现。

这让我感到惊讶的原因是 Dictionary 应该是内部的 HashTable,所以我希望事情以任何顺序出现(按键的哈希排序,对吗?)

我在这里缺少什么? 这是我可以信赖的行为吗?

编辑:好的,我已经想到了可能发生这种情况的许多原因(例如条目的单独列表,这是否是巧合等)。 我的问题是,有人知道这到底是如何工作的吗?

【问题讨论】:

【参考方案1】:

如果您在 3.5 类库上使用 .NET Reflector,您可以看到 Dictionary 的实现实际上将项目存储在一个数组中(根据需要调整大小),并将索引散列到该数组中。获取密钥时,它完全忽略哈希表并遍历项目数组。出于这个原因,您将看到您所描述的行为,因为新项目被添加到数组的末尾。如果您执行以下操作,则看起来像:

add 1
add 2
add 3
add 4
remove 2
add 5

你会得到 1 5 3 4 因为它重复使用了空槽。

重要的是要注意,就像许多其他人一样,您不能指望在未来(或过去)版本中出现这种行为。如果您希望对字典进行排序,则有一个 SortedDictionary 类用于此目的。

【讨论】:

丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁』价格归海豚所有!是的!!谢谢,这实际上是我正在寻找的答案(而不是我得到的答案:“你不懂字典)。感谢您花时间研究。 正如你所说,事实上,删除 2 并添加 5 确实会给你 1 5 3 4 作为结果(我用随机字符串进行测试,而不仅仅是数字)。非常感谢。 使用SortedDictionary 的建议假定用户心中有明确的顺序。如果你想保证插入顺序被保留,你应该实现你自己的IDictionary<TKey, TValue>实现类。【参考方案2】:

字典以散列顺序检索项目。他们按插入顺序出现的事实完全是巧合。

MSDN 文档说:

KeyCollection 中键的顺序未指定,但与 Values 属性返回的 ValueCollection 中关联值的顺序相同。

【讨论】:

好吧,这不是完全巧合。通过一个小样本,我发现它们通常会按照您添加它们的顺序出现。不过,很好的答案! 我认为这不是巧合。我确实改变了添加相同元素的顺序,看看它是否会改变。所以这不是发生的事情。如果我获得足够多的数据,.Net 可能会以不同的方式处理。 如果您阅读了文档,这仍然是您不应该指望的巧合。即使您深入研究代码以了解为什么它对少量键执行此操作,# 也可能会更改,或者在较旧/较新的框架版本中可能永远不会正确。 在这种情况下并非巧合,而是具体未公开的实现细节。【参考方案3】:

你不能指望这种行为,但这并不奇怪。

考虑如何为一个简单的哈希表实现键迭代。您需要遍历所有哈希桶,无论它们是否有任何内容。从大哈希表中获取小数据集可能效率低下。

因此,保留一个单独的重复键列表可能是一个很好的优化。使用双链表,您仍然可以获得恒定时间的插入/删除。 (您可以将哈希表存储桶中的指针保留回该列表。)这样遍历键列表仅取决于条目数,而不取决于存储桶数。

【讨论】:

【参考方案4】:

我认为这来自旧的 .NET 1.1 时代,您有两种字典“ListDictionary”和“HybridDictionary”。 ListDictionary 是一个内部实现为有序列表的字典,推荐用于“小型条目集”。然后你有HybridDictionary,它最初在内部组织为一个列表,但如果它变得大于可配置的阈值,它将成为一个哈希表。这样做是因为历史上正确的基于哈希的字典被认为是昂贵的。现在的日子没有多大意义,但我认为 .NET 只是基于旧的 HybridDictionary 的新 Dictionary 泛型类。

注意:无论如何,正如其他人已经指出的那样,您不应该永远指望任何事情的字典顺序

【讨论】:

【参考方案5】:

来自MSDN 的引用:

键的顺序 字典)>).KeyCollection 是 未指定,但顺序相同 作为关联的值 字典)>).ValueCollection 由 Dictionary)>).Values 属性。

【讨论】:

【参考方案6】:

您在测试中添加了哪些键,按什么顺序添加?

【讨论】:

【参考方案7】:

您的条目可能都在字典中的同一个哈希桶中。每个桶可能是桶中条目的列表。这将解释按顺序返回的条目。

【讨论】:

【参考方案8】:

据我所知,这不应该是一种值得依赖的行为。要快速检查它,请使用相同的元素并更改将它们添加到字典的顺序。你会看看你是按照添加的顺序把它们拿回来的,还是只是巧合。

【讨论】:

我显然是这样做的 :-) 我按照添加它们的相同顺序获取它们,这不是巧合【参考方案9】:

在达到一定的列表大小时,只检查每个条目而不是散列会更便宜。这可能就是正在发生的事情。

添加 100 或 1000 个项目,看看它们是否仍处于相同的顺序。

【讨论】:

3.5 实现将按顺序枚举它们以获取任意数量的元素(查看我的答案了解更多详细信息)。【参考方案10】:

我讨厌这种“设计”的功能。我认为,当给你的班级起一个像“字典”这样的通用名称时,它的行为也应该“像一般预期的那样”。例如 std::map 总是保持它的键值排序。

编辑:显然解决方案是使用 SortedDictionary,其行为类似于 std::map。

【讨论】:

作为接受的答案说明,这是 Microsoft 实施方式的副作用,不能保证。 “正如一般预期的那样”取决于您与谁交谈...类的客户不应假设它是如何实现的,如果需要特定的行为,客户代码应显式实例化适当的类.【参考方案11】:

问题和许多答案似乎误解了哈希表或字典的目的。这些数据结构对于数据结构中包含的项目的值(或实际上是键)的枚举没有指定的行为。

字典或哈希表的目的是能够有效地查找给定已知键的特定值。任何字典或哈希表的内部实现都应该在查找中提供这种效率,但不需要提供关于枚举或值或键上的“每个”类型迭代的任何特定行为。

简而言之,内部数据结构可以按照它希望的任何方式存储和枚举这些值,包括它们被插入的顺序。

【讨论】:

是的,我同意,这就是为什么我首先提出这个问题......鉴于内部实现不关心枚举,并且考虑到我如何理解哈希表在内部工作,那么为什么它仍然被订购?我知道我不应该在意...我只是很好奇什么样的实现会产生这些结果。 (我只是想在这里学习)

以上是关于为啥 .Net 字典中的条目要另外排序?的主要内容,如果未能解决你的问题,请参考以下文章

算法:多路归并的外排序

外排序   败者树   多路归并

外部排序

大话数据结构C语言71 排序方法总结

排序算法-1

Java 实现常见内排序