关于数据结构:为什么条目在.Net字典中按顺序添加?

Why are entries in addition order in a .Net Dictionary?

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

如果我将3或4个元素添加到字典中,然后对每个元素执行"for each"以获取所有键,那么它们将以我添加它们的相同顺序出现。

这让我吃惊的原因是字典在内部应该是一个哈希表,所以我希望事情以任何顺序出现(按键的哈希排序,对吗?)

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

编辑:好的,我已经考虑了很多可能发生这种情况的原因(比如条目的单独列表,是否是巧合等)。我的问题是,有人知道这是怎么回事吗?


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

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

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

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


字典按哈希顺序检索项。事实上,它们是按插入顺序出现的,完全是巧合。

MSDN文档显示:

The order of the keys in the KeyCollection is unspecified, but it is the same order as the associated values in the ValueCollection returned by the Values property.


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

考虑如何为简单哈希表实现键迭代。您将需要遍历所有散列桶,不管其中是否包含任何内容。从一个大哈希表中获取一个小数据集可能效率很低。

因此,最好保留一个单独的、重复的键列表。使用双链接列表,您仍然可以获得恒定的插入/删除时间。(您将保留一个从hashtable bucket返回此列表的指针。)这样,遍历键列表只取决于条目数,而不取决于bucket数。


我认为这来自于旧的.NET 1.1倍的版本,其中有两种字典"listDictionary"和"hybridDictionary"。listDictionary是一个内部作为有序列表实现的字典,建议用于"小的条目集"。然后你有了HybridDictionary,它最初是作为一个列表在内部组织的,但是如果它的增长大于一个可配置的阈值,那么它将成为一个哈希表。这是因为传统的基于哈希的字典被认为是昂贵的。现在的日子没什么意义,但我想.NET只是基于旧的混合字典的新字典通用类。

注:不管怎样,正如其他人已经指出的那样,你不应该指望字典的顺序来做任何事情。


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


您在测试中添加了哪些密钥,它们的顺序是什么?


来自msdn的引用:

The order of the keys in the
Dictionary<(Of <(TKey, TValue>)>).KeyCollection is
unspecified, but it is the same order
as the associated values in the
Dictionary<(Of <(TKey, TValue>)>).ValueCollection
returned by the Dictionary<(Of <(TKey, TValue>)>).Values property.


我讨厌这种"按设计"的功能。我认为当给你的类起一个"字典"这样的通用名称时,它也应该表现得"如一般所预期的"。例如:STD::MAP总是保持它的键值排序。

Eddio:显然解决方案是使用SordDead字典,它的行为类似于STD::MAP。


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

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


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


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

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

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