删除相同的密钥后,C#Dictionary在最后一个索引处不添加新项目?


C# Dictionary not adding new item at last index after remove the same key?

当我从字典中删除一个键,然后我想使用相同的键添加,但新添加的键不在字典的最后一个索引中时,我发现了这个行为。

1
2
3
4
5
6
7
8
9
Dictionary<string, byte> test = new Dictionary<string, byte>();

test.Add("c", 1);  // [{"c", 1}]
test.Add("b", 2);  // [{"c", 1}, {"b", 2}]
test.Add("a", 3);  // [{"c", 1}, {"b", 2}, {"a", 3}]
test.Remove("b");  // [{"c", 1}, {"a", 3}]

test.Add("b", 2);  // [{"c", 1}, {"b", 2}, {"a", 3}] <= why this happen?
                   // [{"c", 1}, {"a", 3}, {"b", 2}] and not this?

我能知道为什么吗?我如何才能使新添加的键成为字典的最后一个索引呢?


字典是哈希表。如果查看哈希表的定义,您会发现哈希表是无序的。

我看了.NET字典实现的具体细节已经有一段时间了,所以在我的故事的其余部分可能会有一些错误——但这是我从细节中记得的:

实现哈希表有很多不同的方案,但是.NET使用的方案类似于带有一些变体的"开放寻址"算法。基本上,新项目被添加到一个列表中(在末尾),哈希表(一个静态数组)在这个列表中添加指针。这就是为什么它实际上似乎保留了秩序。

在某些时候,由于修改或增长,数据将被"垃圾"填满。在这一点上,实施将做一个重新粉饰。如果我记得正确的话,这也是一个点,它将检查是否有太多的碰撞——如果是这样,它将使用一个随机素数将所有哈希值相乘(从而减少碰撞的次数)。真的很优雅。

由于开放寻址方案指向列表中的元素,因此列表中的顺序并不重要。当你列举一本字典时,你基本上看一下这个列表。

您可能想知道为什么它没有枚举哈希代码数组。好吧,哈希表通常分配过度,数据无论如何都存储在另一个列表中。这就意味着这种替代方案的效率要低得多。如果您要枚举哈希表,您可能也会得到一个更一致的结果——但是由于冲突,仍然不会得到一个完全一致的结果。(例如,如果A和B在同一哈希代码上,插入顺序将决定A是跟随B还是反之亦然)。

如果您正在寻找像"set union"这样需要一致排序的算法,我建议使用像SortedDictionary这样的容器。


您可以在这里看到dictionary类的实现代码

如您所见,该实现使用一种跟踪条目数组中自由位置列表的技术,当添加新值时,首先使用自由条目。

框架中有一个非泛型的listDictionary类,我相信它总是在列表的末尾添加新项。请记住,对IDictionary实现的访问通常平均为O(N),而对您当前使用的通用字典的访问平均为O(1)。