我试图更好地理解散列集的内部结构,例如HashSet<T>
,它们是如何工作的,以及为什么它们是高性能的。我发现了下面的文章,实现了一个带有存储桶列表的简单示例http://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/。
据我对这篇文章的理解(我之前也这么认为),桶列表本身对每个桶中的一定数量的元素进行分组。一个桶由哈希码表示,即GetHashCode
在元素上调用它。我认为更好的性能是基于桶比元素少的事实。
现在我编写了以下幼稚的测试代码:
public class CustomHashCode
{
public int Id { get; set; }
public override int GetHashCode()
{
//return Id.GetHashCode(); // Way better performance
return Id % 40; // Bad performance! But why?
}
public override bool Equals(object obj)
{
return ((CustomHashCode) obj).Id == Id;
}
}
这里是探查器:
public static void TestNoCustomHashCode(int iterations)
{
var hashSet = new HashSet<NoCustomHashCode>();
for (int j = 0; j < iterations; j++)
{
hashSet.Add(new NoCustomHashCode() { Id = j });
}
var chc = hashSet.First();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int j = 0; j < iterations; j++)
{
hashSet.Contains(chc);
}
stopwatch.Stop();
Console.WriteLine(string.Format("Elapsed time (ms): {0}", stopwatch.ElapsedMilliseconds));
}
我天真的想法是:让我们减少桶的数量(使用简单的模数),这应该会提高性能。但这很糟糕(在我的系统上,50000 次迭代大约需要 4 秒)。我还认为,如果我只是将 Id 作为哈希码返回,性能应该会很差,因为我最终会得到 50000 个桶。但情况恰恰相反,我想我只是产生了所谓的碰撞音,而不是改进任何东西。但话又说回来,遗愿清单是如何工作的?