【发布时间】:2011-04-01 02:17:01
【问题描述】:
我正在使用一个应用程序,它使用了许多大型字典(最多 10^6 个元素),其大小事先是未知的(尽管在某些情况下我可以猜到)。我想知道字典是如何实现的,即如果我不给出字典大小的初始估计,效果会有多糟糕。它是否像 List 那样在内部使用(自增长)数组?在这种情况下,让字典增长可能会在 LOH 上留下大量未引用的大型数组。
【问题讨论】:
标签: c# dictionary
我正在使用一个应用程序,它使用了许多大型字典(最多 10^6 个元素),其大小事先是未知的(尽管在某些情况下我可以猜到)。我想知道字典是如何实现的,即如果我不给出字典大小的初始估计,效果会有多糟糕。它是否像 List 那样在内部使用(自增长)数组?在这种情况下,让字典增长可能会在 LOH 上留下大量未引用的大型数组。
【问题讨论】:
标签: c# dictionary
使用Reflector,我发现了以下内容: Dictionary 将数据保存在结构数组中。它会计算该数组中剩下多少空位。当您添加一个项目并且没有剩余空间时,它会增加内部数组的大小(见下文)并将数据从旧数组复制到新数组。
所以如果你知道会有很多条目,我建议你应该使用设置初始大小的构造函数。
编辑:逻辑实际上很有趣:有一个名为HashHelpers 的内部类来查找素数。为了加快速度,它还在一个静态数组中存储了一些素数,从 3 到 7199369(有些缺失;原因见下文)。当您提供容量时,它会从数组中找到下一个素数(相同或更大的值),并将其用作初始容量。如果你给它一个比它的数组更大的数字,它就会开始手动检查。
因此,如果没有任何内容作为容量传递给字典,则起始容量为 3。
一旦超过容量,它将当前容量乘以 2,然后使用辅助类找到下一个更大的素数。这就是为什么在数组中并不是每个素数都需要,因为素数“靠得太近”并不是真正需要的。
所以如果我们不传递初始值,我们会得到(我检查了内部数组):
一旦我们通过了这个大小,下一步就会落在内部数组之外,它将手动搜索更大的素数。这会很慢。您可以使用 7199369(数组中的最大值)进行初始化,或者考虑一下 Dictionary 中有超过 500 万个条目是否意味着您应该重新考虑您的设计。
【讨论】:
MSDN 说:“使用它的键检索一个值非常快,接近 O(1),因为 Dictionary 类是作为哈希表实现的。”以及“通过重新分配内部数组,根据需要自动增加容量。”
但是,如果您给出初步估计,您会得到更少的重新分配。如果您从一开始就拥有所有项目,那么 LINQ 方法 ToDictionary 可能会很方便。
【讨论】:
ToDictionary 根本不预先分配字典 - 它所做的只是一次添加一个元素,直到完成。如果您提前知道(或可以猜到)字典的大小,您最好自己创建字典并自己迭代添加它们。
哈希表通常有一个称为负载因子的东西,如果达到这个阈值,它将增加后备桶存储。 IIRC 默认值为 0.72。如果你有完美的散列,这可以增加到 1.0。
此外,当哈希表需要更多桶时,必须重新哈希整个集合。
【讨论】:
【讨论】:
{
"Details":
{
"ApiKey": 50125
}
}
public Dictionary<string, string> Details{ get; set; }
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;
}
}
【讨论】: