【问题标题】:Edge-cases of Dictionary/Hastable broken order (need existing examples)Dictionary/Hastable 断序的边缘案例(需要现有示例)
【发布时间】:2017-12-12 19:47:19
【问题描述】:

我已经阅读了官方 MSDN doc 和多个关于 Dictionary/Hashset 和其他无序集合顺序问题的 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 并发集合,我对理论答案不感兴趣,I've read them.

我正在寻找test.ElementAt(5).key 将返回key11115 的单个示例。

【问题讨论】:

  • Dictionary&lt;TKey, TValue&gt; 的源代码在here 可用。如果您查看Enumerator.MoveNext(),您会看到它按顺序返回dictionary.entries
  • 如果您查看private void Insert(TKey key, TValue value, bool add),您会发现只要没有空闲条目,添加的项目就会放在entries 数组的末尾,并且重新散列不会改变这个顺序。所以看来,有了这个实现,只要什么都没有被删除,字典就会保留添加东西的顺序。
  • 但这只是一个实现细节。 Dictionary&lt;TKey, TValue&gt; 在单声道上的版本(或一些未来的 .Net 版本,比如 .Net core 3.5 或 .Net full 5.2 或无论如何)可以重写,以便重新散列字典更改顺序。 documentation 做出的唯一承诺是返回项目的顺序是不确定的,所以再依赖任何东西都是不明智的。
  • 所以,鉴于您现在所说的,请告诉我这句话是否属实:"You can rely on Dictionary order under given restrictions to solve single current task at hand right now with its current implementation". 不是在谈论当 DT/HT 实施中断/更改时可能会中断的长期实施。另外,作为答案发布。

标签: c# dictionary


【解决方案1】:

Dictionary&lt;TKey, TValue&gt; 的源代码在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);

因此,我们可以说有了这个实现并且只要没有删除任何内容,字典就会保留添加键的顺序。

但这只是一个实现细节。单声道上的Dictionary&lt;TKey, TValue&gt; 版本(或某些未来的.Net 版本,例如.Net core 3.5 或.Net full 5.2 或其他)可以重写这样重新散列字典就会改变顺序。 documentation 做出的唯一承诺是返回项目的顺序是不确定的,所以再依赖任何东西都是不明智的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-28
    • 2019-07-29
    • 2016-03-30
    • 2012-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-18
    相关资源
    最近更新 更多