很明显,泛型HashSet<T>类的搜索性能要高于泛型List<T>类。只需将基于哈希的键与List<T>类中的线性方法进行比较。
然而,计算哈希键本身可能需要一些CPU周期,因此对于少量的项,线性搜索可以成为HashSet<T>的真正替代方法。
我的问题是:盈亏平衡在哪里?
为了简化场景(公平起见),让我们假设List<T>类使用元素的Equals()方法来标识一个项。
很明显,泛型HashSet<T>类的搜索性能要高于泛型List<T>类。只需将基于哈希的键与List<T>类中的线性方法进行比较。
然而,计算哈希键本身可能需要一些CPU周期,因此对于少量的项,线性搜索可以成为HashSet<T>的真正替代方法。
我的问题是:盈亏平衡在哪里?
为了简化场景(公平起见),让我们假设List<T>类使用元素的Equals()方法来标识一个项。
当前回答
视情况而定。如果确切的答案真的很重要,那就做一些分析,找出答案。如果你确定你永远不会有超过一定数量的元素在集合中,使用List。如果数字是无界的,则使用HashSet。
其他回答
很多人都说,一旦你达到了速度实际上是一个问题的大小,HashSet<T>将总是击败List<T>,但这取决于你在做什么。
假设你有一个List<T>,其中平均只有5个项目。在大量的循环中,如果每个循环添加或删除一个项目,那么使用List<T>可能会更好。
我在我的机器上做了一个测试,它必须非常非常小才能从List< t>中获得优势。对于一个短字符串列表,在大小为5之后,对于大小为20之后的对象,优势就消失了。
1 item LIST strs time: 617ms
1 item HASHSET strs time: 1332ms
2 item LIST strs time: 781ms
2 item HASHSET strs time: 1354ms
3 item LIST strs time: 950ms
3 item HASHSET strs time: 1405ms
4 item LIST strs time: 1126ms
4 item HASHSET strs time: 1441ms
5 item LIST strs time: 1370ms
5 item HASHSET strs time: 1452ms
6 item LIST strs time: 1481ms
6 item HASHSET strs time: 1418ms
7 item LIST strs time: 1581ms
7 item HASHSET strs time: 1464ms
8 item LIST strs time: 1726ms
8 item HASHSET strs time: 1398ms
9 item LIST strs time: 1901ms
9 item HASHSET strs time: 1433ms
1 item LIST objs time: 614ms
1 item HASHSET objs time: 1993ms
4 item LIST objs time: 837ms
4 item HASHSET objs time: 1914ms
7 item LIST objs time: 1070ms
7 item HASHSET objs time: 1900ms
10 item LIST objs time: 1267ms
10 item HASHSET objs time: 1904ms
13 item LIST objs time: 1494ms
13 item HASHSET objs time: 1893ms
16 item LIST objs time: 1695ms
16 item HASHSET objs time: 1879ms
19 item LIST objs time: 1902ms
19 item HASHSET objs time: 1950ms
22 item LIST objs time: 2136ms
22 item HASHSET objs time: 1893ms
25 item LIST objs time: 2357ms
25 item HASHSET objs time: 1826ms
28 item LIST objs time: 2555ms
28 item HASHSET objs time: 1865ms
31 item LIST objs time: 2755ms
31 item HASHSET objs time: 1963ms
34 item LIST objs time: 3025ms
34 item HASHSET objs time: 1874ms
37 item LIST objs time: 3195ms
37 item HASHSET objs time: 1958ms
40 item LIST objs time: 3401ms
40 item HASHSET objs time: 1855ms
43 item LIST objs time: 3618ms
43 item HASHSET objs time: 1869ms
46 item LIST objs time: 3883ms
46 item HASHSET objs time: 2046ms
49 item LIST objs time: 4218ms
49 item HASHSET objs time: 1873ms
下面是以图表形式显示的数据:
代码如下:
static void Main(string[] args)
{
int times = 10000000;
for (int listSize = 1; listSize < 10; listSize++)
{
List<string> list = new List<string>();
HashSet<string> hashset = new HashSet<string>();
for (int i = 0; i < listSize; i++)
{
list.Add("string" + i.ToString());
hashset.Add("string" + i.ToString());
}
Stopwatch timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
list.Remove("string0");
list.Add("string0");
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item LIST strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
hashset.Remove("string0");
hashset.Add("string0");
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item HASHSET strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine();
}
for (int listSize = 1; listSize < 50; listSize+=3)
{
List<object> list = new List<object>();
HashSet<object> hashset = new HashSet<object>();
for (int i = 0; i < listSize; i++)
{
list.Add(new object());
hashset.Add(new object());
}
object objToAddRem = list[0];
Stopwatch timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
list.Remove(objToAddRem);
list.Add(objToAddRem);
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item LIST objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
timer = new Stopwatch();
timer.Start();
for (int i = 0; i < times; i++)
{
hashset.Remove(objToAddRem);
hashset.Add(objToAddRem);
}
timer.Stop();
Console.WriteLine(listSize.ToString() + " item HASHSET objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine();
}
Console.ReadLine();
}
这取决于很多因素……列表实现,CPU架构,JVM,循环语义,equals方法的复杂性,等等…当列表变得足够大,可以有效地进行基准测试(1000多个元素)时,基于哈希的二进制查找就可以轻松地击败线性搜索,并且差异只会在此基础上扩大。
希望这能有所帮助!
盈亏平衡将取决于计算散列的成本。哈希计算可以是微不足道的,或者不是…:-)总有System.Collections.Specialized.HybridDictionary类帮助你不必担心盈亏平衡点。
视情况而定。如果确切的答案真的很重要,那就做一些分析,找出答案。如果你确定你永远不会有超过一定数量的元素在集合中,使用List。如果数字是无界的,则使用HashSet。
使用HashSet<>还是List<>取决于您需要如何访问您的集合。如果你需要保证项目的顺序,使用一个列表。如果没有,请使用HashSet。让微软去担心他们的哈希算法和对象的实现吧。
HashSet将访问项目而不必枚举集合(复杂度为O(1)或接近它),并且由于List保证顺序,与HashSet不同,一些项目将必须被枚举(复杂度为O(n))。