【问题标题】:How is the c#/.net 3.5 dictionary implemented?c#/.net 3.5字典是如何实现的?
【发布时间】:2011-04-01 02:17:01
【问题描述】:

我正在使用一个应用程序,它使用了许多大型字典(最多 10^6 个元素),其大小事先是未知的(尽管在某些情况下我可以猜到)。我想知道字典是如何实现的,即如果我不给出字典大小的初始估计,效果会有多糟糕。它是否像 List 那样在内部使用(自增长)数组?在这种情况下,让字典增长可能会在 LOH 上留下大量未引用的大型数组。

【问题讨论】:

标签: c# dictionary


【解决方案1】:

使用Reflector,我发现了以下内容: Dictionary 将数据保存在结构数组中。它会计算该数组中剩下多少空位。当您添加一个项目并且没有剩余空间时,它会增加内部数组的大小(见下文)并将数据从旧数组复制到新数组。

所以如果你知道会有很多条目,我建议你应该使用设置初始大小的构造函数。

编辑:逻辑实际上很有趣:有一个名为HashHelpers 的内部类来查找素数。为了加快速度,它还在一个静态数组中存储了一些素数,从 3 到 7199369(有些缺失;原因见下文)。当您提供容量时,它会从数组中找到下一个素数(相同或更大的值),并将其用作初始容量。如果你给它一个比它的数组更大的数字,它就会开始手动检查。

因此,如果没有任何内容作为容量传递给字典,则起始容量为 3。

一旦超过容量,它将当前容量乘以 2,然后使用辅助类找到下一个更大的素数。这就是为什么在数组中并不是每个素数都需要,因为素数“靠得太近”并不是真正需要的。

所以如果我们不传递初始值,我们会得到(我检查了内部数组):

  1. 3
  2. 7
  3. 17
  4. 37
  5. 71
  6. 163
  7. 353
  8. 761
  9. 1597
  10. 3371
  11. 7013
  12. 14591
  13. 30293
  14. 62851
  15. 130363
  16. 270371
  17. 560689
  18. 1162687
  19. 2411033
  20. 4999559

一旦我们通过了这个大小,下一步就会落在内部数组之外,它将手动搜索更大的素数。这会很慢。您可以使用 7199369(数组中的最大值)进行初始化,或者考虑一下 Dictionary 中有超过 500 万个条目是否意味着您应该重新考虑您的设计。

【讨论】:

  • 对于 10^6 个元素,大约需要 20 次重新散列。
  • 列表从容量 4 开始,我认为字典也是。
  • @Albin 取决于实际数量,在 16 到 20 之间重新散列。 @Rangoric 它从 3 点开始。
  • 很好的答案,但是我们如何在内部数组中选择索引来放置我们的值?我们是否使用函数 GetHasCode 并使用它根据内部数组大小划分的值来查找我们的值的索引?
  • 在增加容量时始终使用素数有什么意义或优势?
【解决方案2】:

MSDN 说:“使用它的键检索一个值非常快,接近 O(1),因为 Dictionary 类是作为哈希表实现的。”以及“通过重新分配内部数组,根据需要自动增加容量。”

但是,如果您给出初步估计,您会得到更少的重新分配。如果您从一开始就拥有所有项目,那么 LINQ 方法 ToDictionary 可能会很方便。

【讨论】:

  • ToDictionary 根本不预先分配字典 - 它所做的只是一次添加一个元素,直到完成。如果您提前知道(或可以猜到)字典的大小,您最好自己创建字典并自己迭代添加它们。
【解决方案3】:

哈希表通常有一个称为负载因子的东西,如果达到这个阈值,它将增加后备桶存储。 IIRC 默认值为 0.72。如果你有完美的散列,这可以增加到 1.0。

此外,当哈希表需要更多桶时,必须重新哈希整个集合。

【讨论】:

    【解决方案4】:

    对我来说最好的方法是使用 .NET Reflector。

    http://www.red-gate.com/products/reflector/

    使用反汇编代码查看实现。

    【讨论】:

      【解决方案5】:
      • JSON 作为字典
      {
       "Details": 
          { 
          "ApiKey": 50125
          }
      }
      
      • 模型应包含 Dictionary 类型的属性。
      public Dictionary<string, string> Details{ get; set; }
      
      • 实现 foreach() 块,数据类型为“KeyValue”
             foreach (KeyValuePair<string, string> dict in Details)
                      {
                          switch (dict.Key)
                          {
                            case nameof(settings.ApiKey):
                            int.TryParse(kv.Value, out int ApiKey);
                            settings.ApiKey=ApiKey;
                                  break;
                              default:
                                  break;
                             }
                         }
      
      

      【讨论】:

        猜你喜欢
        • 2011-03-13
        • 2019-09-29
        • 1970-01-01
        • 1970-01-01
        • 2013-04-10
        • 2013-03-31
        • 2011-10-16
        • 2022-06-12
        • 2023-04-10
        相关资源
        最近更新 更多