Dictionary / Hastable破坏顺序的边缘情况(需要现有示例)

时间:2017-12-12 19:47:19

标签: c# dictionary

我已经阅读了有关Dictionary / Hashset和其他无序收集订单问题的官方MSDN doc和多个SO问题。这个问题有点不同 - 我正在寻找实际的实例而不是理论

条件:

  • 字典/ hastable条目只能添加
  • 字典/ hastable条目永远不会被删除(除非在开始时清除)
  • 字典/ hastable仅以VALUE TYPES运行
  • 字典/ hastable永远不会以任何其他方式更改,但添加元素
  • 字典/ hastable始终从0开始,索引通过简单的代码逻辑保留
  • 字典密钥本身(密钥名称)永远不会手动更改

代码示例:

Dictionary<int, int> test = new Dictionary<int, int>();
for (int i = 0; i <= 100000; i++)
{
    test.Add(i,i);
}
var c = 0;
while (c < test.Count)
{
    if (c != test.ElementAt(c).Key)
    {
        Console.WriteLine("Mismatch!");
        break;
    }
    c++;
}
Console.WriteLine("Test passed!");
//Program takes a 2hrs walk, does things, occasionally reads-only values from existing "test" collection
for (int i = 0; i <= 500000; i++)
{
    test[test.Count] = test.Count;
}
c = 0;
while (c < test.Count)
{
    if (c != test.ElementAt(c).Key)
    {
        Console.WriteLine("Mismatch!");
        break;
    }
    c++;
}
Console.WriteLine("Test 2 passed!");
//repeat some stuff 

问题: 根据上面的严格规则 - 当这种字典随机改变其元素的顺序时,有没有人遇到过这种情况?我们不是一次又一次地谈论MT Concurrent Collection,我对理论答案不感兴趣,I've read them.

test.ElementAt(5).key返回key 11115时的一个示例是我正在寻找的。

1 个答案:

答案 0 :(得分:1)

Dictionary<TKey, TValue>的源代码可用here。如果您查看Enumerator.MoveNext(),您会看到它按顺序遍历数组dictionary.entries

while ((uint)index < (uint)dictionary.count) {
    if (dictionary.entries[index].hashCode >= 0) {
        current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value);
        index++;
        return true;
    }
    index++;
}

如果您查看private void Insert(TKey key, TValue value, bool add)(在添加和设置项目时都会调用),只要没有空闲条目(即没有删除任何内容),您就会看到 如果找不到密钥,则项目放在entries数组的末尾;如果找到密钥,则放在当前位置:

private void Insert(TKey key, TValue value, bool add) {    
// Snip 
    int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
    int targetBucket = hashCode % buckets.Length;

// Snip 
    for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {
        if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
// Key found.  Set the new value at the old location in the entries array.
            if (add) { 
                ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
            }
            entries[i].value = value;
            version++;
            return;
        }  
        // Snip 
    }
// Key not found, add to the dictionary.
    int index;
    if (freeCount > 0) {
// Free entries exist because items were previously removed; recycle the position in the entries array.
        index = freeList;
        freeList = entries[index].next;
        freeCount--;
    }
    else {
// No free entries.  Add to the end of the entries array, resizing if needed.
        if (count == entries.Length)
        {
            Resize();
            targetBucket = hashCode % buckets.Length;
        }
        index = count;
        count++;
    }

// Set the key and value in entries array.
    entries[index].hashCode = hashCode;
    entries[index].next = buckets[targetBucket];
    entries[index].key = key;
    entries[index].value = value;
    buckets[targetBucket] = index;
// Snip remainder

最后,如果你检查Resize(),调用rehash字典的方法,你会看到entries数组的顺序被保留:

    Array.Copy(entries, 0, newEntries, 0, count);

因此,我们可以说使用此实现,只要没有删除,字典会保留添加键的顺序。

但这仅仅是一个实现细节。单声道(或某些未来.Net版本,例如.Net核心3.5或.Net完整5.2或其他)的Dictionary<TKey, TValue>版本可以被重写,以便重新翻译字典改变顺序。 documentation唯一的承诺是返回项目的顺序是未定义的因此依赖其他任何东西是不明智的。