什么.NET集合提供最快的搜索?

163

我有60,000个项目需要与20,000个查找列表进行匹配。是否有一种集合对象(例如ListHashTable),它提供了异常快速的Contains()方法?还是我必须编写自己的代码?换句话说,默认的Contains()方法只是扫描每个项目,还是使用更好的搜索算法。

foreach (Record item in LargeCollection)
{
    if (LookupCollection.Contains(item.Key))
    {
       // Do something
    }
}

注意。查找列表已经排序。


包含(Contains)对于对象列表不起作用,因为它在比较引用。 - Fiur
2
排好序的数据?二分查找 - 参见@Mark的答案。 - Hamish Smith
根据我的经验,HashTable 在处理多达2百万个项目时非常出色。 - Chris S
顺便提一下,如果你的元素有意义的顺序并且分布相对均匀,你可以通过将第一次猜测设定在估计范围内来更快地进行二分查找。这可能与您的特定应用程序有关或无关。 - Brian
2
如果你想简化这个过程但又要避免使用哈希集合,不要忘记 System.Collections.Generic.SortedList(TKey, TValue)。 - Brian
我很好奇,你为什么想要避免使用哈希集? - Benjamin Chambers
9个回答

167

在最一般的情况下,将 System.Collections.Generic.HashSet 视为默认的“包含”工具数据结构,因为它需要恒定时间来评估 Contains

“最快的可搜索集合是什么”这个问题的实际答案取决于您的具体数据大小、有序性、散列成本和搜索频率。


41
请勿忘记重写hashcode函数。为了提高性能,在构造函数中预生成您的hashcode。 - Brian
1
@Brian:说得好。我在毫无根据的情况下假设Record.Key是某种内置类型。 - Jimmy
3
@Brian:与其预先生成,我更喜欢在第一次生成后将其存储,为什么要用一些可能不会被使用的东西拖慢构造函数的速度呢? - jmservera
10
FYI:性能测试 - 我对字符串的List<T>和HashSet<T>进行了比较。我发现HashSet比List快了大约1000倍。 - Quango
12
@Quango: 三年后了,但如果你不指定数据集的大小,这个性能比较就没有意义:哈希集合的搜索是O(1),列表的搜索是O(n),因此性能比例与n成正比。 - Clément
显示剩余4条评论

78
如果您不需要排序,可以尝试使用HashSet<Record>(.Net 3.5中新增)。
如果需要排序,则使用List<Record>并调用BinarySearch

9
或者在 .NET >= 4 中使用 SortedSet - StriplingWarrior
2
或者更好的选择是,来自 System.ImmutableCollections 的 ImmutableSortedSet。 - Alexei S

26

您考虑过使用List.BinarySearch(item)吗?

您说您的大型集合已经排序,因此这似乎是一个完美的机会?哈希肯定是最快的,但这也带来了自己的问题,并需要更多的存储开销。


1
你是对的,当使用可变对象作为键时,哈希可能会带来一些不良问题。 - jmservera

13

你应该阅读这篇博客,它对多种不同类型的集合和方法进行了速度测试,并使用单线程和多线程技术。

根据结果,在查找“值”时,List和SortedList上的BinarySearch始终是表现最好的。

当使用允许“键”的集合时,Dictionary、ConcurrentDictionary、Hashset和HashTables总体表现最佳。


9
我做了一个测试:
  • 首先-使用A-Z0-9的所有可能的组合生成3个字符
  • 使用这些字符串填充此处提到的每个集合
  • 最后-为每个集合搜索并计时随机字符串(每个集合相同)。

此测试模拟了在有保证有结果的情况下进行查找。

FullCollection

然后,我将初始集合从所有可能的组合更改为仅包含10,000个随机3个字符组合,这应该产生1/4.6的随机3字符查找命中率,因此这是一项没有保证结果的测试,并再次运行了测试:

PartialCollection

我认为HashTable虽然速度最快,但不总是最方便的;使用对象工作时HashSet非常接近,可能是要推荐的方法。

只是为了好玩(你知道FUN),我尝试了1.68M行(4个字符):

BiggerCollection

4

保持列表x和y的排序。

如果x = y,则执行您的操作,如果x < y,则将x前进,如果y < x,则将y前进,直到任一列表为空。

此交集的运行时间与min(size(x),size(y))成比例。

不要运行.Contains()循环,这是与x * y成比例的,这更糟糕。


+1 对于更高效的算法。 即使列表当前是未排序的,先对它们进行排序然后再运行这个算法会更加高效。 - Matt Boehm
最坏情况下,运行时间是否与max(size(x),size(y))成正比呢?例如: int[] x = {99,100}; int[] y = {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; - Matt Boehm
不是因为一旦你完成了较小的集合,你就可以将剩余的元素从较大的集合中附加上去,因为它们已经排序好了。我认为这个过程类似于归并排序。 - Brig Lamoreaux

3
如果您的项是可排序的,那么有一种比在哈希表或B树中查找键更快的方法。但是如果您的项目不能进行排序,那么您实际上也无法将它们放入B树中。
无论如何,如果可排序,请对两个列表进行排序,然后按顺序遍历查找列表即可。
Walk lookup list
   While items in check list <= lookup list item
     if check list item = lookup list item do something
   Move to next lookup list item

是的,非常正确。如果你有两个已排序的列表,你只需要遍历每个列表一次。 - denver

3
如果您正在使用 .Net 3.5,您可以使用以下代码来编写更加简洁的代码:
foreach (Record item in LookupCollection.Intersect(LargeCollection))
{
  //dostuff
}

我这里没有 .Net 3.5,所以这个还没有测试。它依赖于一个扩展方法。注意,LookupCollection.Intersect(LargeCollection) 可能与 LargeCollection.Intersect(LookupCollection) 不同...后者可能会慢得多。

这假设 LookupCollection 是一个 HashSet


2
如果你不担心性能的问题,使用HashSet或二分搜索是一个很好的建议。对于你的数据集来说,99%的情况下这都不会成为问题。
但如果这只是你要做的数千次中的一次,并且性能至关重要(并且使用HashSet/二分搜索已经被证明无法满足需求),你可以编写自己的算法,遍历排序后的列表并在进行比较时进行优化。每个列表最多只需要遍历一次,在病态情况下也不会太糟糕(一旦你采用了这种方法,你可能会发现比较操作,假设它是字符串或其他非整数值,才是真正的开销,而优化它将是下一步)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接