【发布时间】:2012-09-28 13:12:05
【问题描述】:
我不确定在这里问这个问题是否正确,但请不要杀了我:)
我和一个朋友就 C# 的字典发生了争执…… 她告诉我,如果我让我们说带有 1 个元素的字典。并且键的哈希码是 100000,那么字典的内部数组将是 100000 的大小!
这是真的吗?我试图在 Google 上找到答案,但由于某种原因我没有找到该问题的答案。
【问题讨论】:
标签: c# arrays dictionary hashcode internal
我不确定在这里问这个问题是否正确,但请不要杀了我:)
我和一个朋友就 C# 的字典发生了争执…… 她告诉我,如果我让我们说带有 1 个元素的字典。并且键的哈希码是 100000,那么字典的内部数组将是 100000 的大小!
这是真的吗?我试图在 Google 上找到答案,但由于某种原因我没有找到该问题的答案。
【问题讨论】:
标签: c# arrays dictionary hashcode internal
字典的默认构造函数,“具有默认初始容量”,according to MSDN。
它还指出:
如果您可以估计集合的大小,则使用指定初始容量的构造函数可以消除在向字典中添加元素时执行大量调整大小操作的需要。
这样的构造函数只需要一个Int32,它会按如下方式初始化内部存储:
字典可以包含的元素的初始数量。
字典的“默认初始容量”实际上是类的内部实现细节,因此不会在文档或公共 API 中公开。
将mscorlib与ilspy反汇编,查看默认构造函数,发现其实现如下:
public Dictionary() : this(0, null)
{
}
该链式构造函数实现如下:
public Dictionary(int capacity, IEqualityComparer<TKey> comparer)
{
if (capacity < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
}
if (capacity > 0)
{
this.Initialize(capacity);
}
this.comparer = (comparer ?? EqualityComparer<TKey>.Default);
}
即Initialize() 根本不被默认构造函数直接或间接调用。
Initialize()是设置内部存储的方法。
实际上,如果您调用默认构造函数,内部存储大小甚至在您首次添加项目之前都不会初始化。所以它的大小基本上为零。
Initialize() 在您第一次调用.Add() 时最终会以零值调用,
设置好了。
private void Initialize(int capacity)
{
int prime = HashHelpers.GetPrime(capacity);
this.buckets = new int[prime];
for (int i = 0; i < this.buckets.Length; i++)
{
this.buckets[i] = -1;
}
this.entries = new Dictionary<TKey, TValue>.Entry[prime];
this.freeList = -1;
}
GetPrime(0) 返回3,因此this.buckets 设置为包含三个整数的数组。
为this.entries 赋值的那行看起来有点奇怪,但我看不出 100000 的位置。
简答题
我认为你的同事错了。
【讨论】:
哈希码(即GetHashCode)用于将项目放入字典使用的桶中。
实际使用的容量取决于字典中元素的数量。
GetHashCode 用于的(可能不准确)伪代码是这样的。
List<List<KeyValuePair<T,J>>> buckets; // let's assume this get's allocated somewhere (the dictionary allocates this internally)
...
public J GetValueFromDictionary(T key)
{
int bucketIndex = key.GetHashCode() % buckets.Length;
return buckets[bucketIndex].Find(x => x.Key == key).Single().Value;
}
【讨论】:
不,不是。 Dictionary<,> 类的来源证明了这一点。
【讨论】:
只需使用反射器反编译代码并自行验证即可。
【讨论】:
几乎没有这种情况,因为字典的存储比这复杂得多。此外,哈希码的值,可能是关键,不以任何方式定义字典的大小(我什至不知道怎么可能捏造的)。
现在,让我们分解字典的存储。
只要一个对象被用作字典中的键,它就不能以任何影响其哈希值的方式发生变化。根据字典的相等比较器,字典中的每个键都必须是唯一的。键不能是空引用(在 Visual Basic 中为 Nothing),但值可以是,如果值类型 TValue 是引用类型。
字典需要一个相等的实现来确定键是否相等。您可以使用接受比较器参数的构造函数来指定 IEqualityComparer 通用接口的实现;如果您未指定实现,则使用默认的通用相等比较器 EqualityComparer.Default。如果类型 TKey 实现了 System.IEquatable 泛型接口,则默认相等比较器使用该实现。
虽然很可能会使用哈希码,因为EqualityComparer.Default 是这样定义的:
Default 属性检查类型 T 是否实现了 System.IEquatable 泛型接口,如果是,则返回使用该实现的 EqualityComparer。 否则它会返回一个使用 T 提供的 Object.Equals 和 Object.GetHashCode 覆盖的 EqualityComparer。
不保证 是 将成为密钥的生成方式。所以,我希望这对你的论点有所帮助。
归根结底,哈希码无法确定字典的内部大小,字典是可变的,并且随着项目的添加而增长,如 Microsoft 所述:
字典的容量是字典可以容纳的元素数量。随着元素被添加到字典中,容量会根据需要通过重新分配内部数组来自动增加。
你的朋友在吵架之前需要做她的研究。呼!
【讨论】:
作为更新,现在微软提供了 .NET 实现的参考代码,这比反编译更容易。这个问题具体请看
https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs https://referencesource.microsoft.com/#mscorlib/system/collections/hashtable.cs
您可以看到默认容量为3,如前面的答案所述。
【讨论】:
不,这不是真的。如果我有
Dictionary<int, object[]> dict = new Dictionary<int, object[]>()
{
{10000, new object[] { 1, 2, 3, 4 }}
};
这个字典将包含一个索引为 10000 的对象数组,而不是 9999 个空对象数组插槽,后面跟着我们上面输入的对象。答案是否定的,你朋友错了。
我希望这会有所帮助。
【讨论】: