[英]Fastest way to populate a Hashset
我需要定期遍历大量对象并维护其中特定String属性的唯一值。
我正在使用Hashset来保存唯一值,但是想知道检查Hashset中是否存在值是否更有效,或者只是尝试添加所有值?
你的测试是一个糟糕的测试,因为Jon Hanna说的原因并没有给你准确的结果。 当你调用Add
AddIfNotPresent
HashSet调用AddIfNotPresent
, AddIfNotPresent
做的第一件事是检查对象是否存在(代码来自ILSpy)
public bool Add(T item)
{
return this.AddIfNotPresent(item);
}
private bool AddIfNotPresent(T value)
{
if (this.m_buckets == null)
{
this.Initialize(0);
}
int num = this.InternalGetHashCode(value);
int num2 = num % this.m_buckets.Length;
int num3 = 0;
for (int i = this.m_buckets[num % this.m_buckets.Length] - 1; i >= 0; i = this.m_slots[i].next)
{
if (this.m_slots[i].hashCode == num && this.m_comparer.Equals(this.m_slots[i].value, value))
{
return false;
}
num3++;
}
//(Snip)
因此,通过Contains
然后Add
您可以检查对象是否存在两次 。 如果您在存储桶中有许多项目,则检查这可能会导致严重的性能损失。
由于我原来的答案一般受到嘲笑,我还有另外一步。
Int32 maxUniques = 1;
Int32 collectionSize = 100000000;
Random rand = new Random();
while (maxUniques <= collectionSize)
{
List<Int32> bigCollection = new List<Int32>();
bigCollection.Capacity = collectionSize;
for (Int32 count = 0; count < collectionSize; count++)
bigCollection.Add(rand.Next(maxUniques));
HashSet<Int32> uniqueSources = new HashSet<Int32>();
Stopwatch watch = new Stopwatch();
watch.Start();
foreach (Int32 num in bigCollection)
{
if (!uniqueSources.Contains(num))
uniqueSources.Add(num);
}
Console.WriteLine(String.Format("With {0,10:N0} unique values in a set of {1,10:N0} values, the time taken for conditional add: {2,6:N0} ms", uniqueSources.Count, collectionSize, watch.ElapsedMilliseconds));
uniqueSources = new HashSet<Int32>();
watch.Restart();
foreach (Int32 num in bigCollection)
{
uniqueSources.Add(num);
}
Console.WriteLine(String.Format("With {0,10:N0} unique values in a set of {1,10:N0} values, the time taken for simple add: {2,6:N0} ms", uniqueSources.Count, collectionSize, watch.ElapsedMilliseconds));
Console.WriteLine();
maxUniques *= 10;
}
其中给出了以下输出:
使用100,000,000个值中的1个唯一值,条件添加所需的时间:2,004 ms在100,000,000个值的集合中有1个唯一值,简单添加所需的时间:2,540 ms
在一组100,000,000个值中有10个唯一值,条件添加所需的时间:2,066 ms在一组100,000,000个值中有10个唯一值,简单添加所需的时间:2,391 ms
在一组100,000,000个值中有100个唯一值,条件添加所需的时间:2,057 ms在100,000,000个值的100个唯一值中,简单添加所需的时间:2,410 ms
在一组100,000,000个值中有1,000个唯一值,条件添加所需的时间:2,011 ms在100,000,000个值中有1,000个唯一值,简单添加所需的时间:2,459 ms
在一组100,000,000个值中有10,000个唯一值,条件添加所需的时间为:2,219 ms
在一组100,000,000个值中有10,000个唯一值,简单添加所需的时间:2,414 ms在一组100,000,000个值中有100,000个唯一值,条件添加所需的时间为:3,024 ms
在一组100,000,000个值中有100,000个唯一值,简单添加所需的时间:3,124 ms在一组100,000,000个值中有1,000,000个唯一值,条件添加所需的时间为:8,937 ms
在一组100,000,000个值中有1,000,000个唯一值,简单添加所需的时间:9,310 ms在一组100,000,000个值中有9,999,536个唯一值,有条件地添加所需的时间:11,798 ms
在一组100,000,000个值中有9,999,536个唯一值,简单添加所需的时间:11,660 ms在一组100,000,000个值中有63,199,938个唯一值,条件添加所需的时间为:20,847 ms
在一组100,000,000个值中有63,199,938个唯一值,简单添加所需的时间:20,213 ms
这对我很好奇。
添加最多1%,调用Contains()方法更快,而不是仅仅按下Add()。 对于10%和63%,只有Add()更快。
换一种方式:
1亿个Contains()比9900万个Add()更快
1亿个Contains()比9000万个Add()慢
我调整了代码,以100万增量尝试100万到1000万个唯一值,并发现拐点大约在7-10%左右,结果并不是决定性的。
因此,如果您希望添加的值少于7%,则首先调用Contains()会更快。 超过7%,只需调用Add()即可。
当我输入问题时,有人会问我为什么不自己测试它。 所以我自己测试了一下。
我创建了一个包含126万条记录和21个独特源代码的集合,并通过以下代码运行它:
HashSet<String> uniqueSources = new HashSet<String>();
Stopwatch watch = new Stopwatch();
watch.Start();
foreach (LoggingMessage mess in bigCollection)
{
uniqueSources.Add(mess.Source);
}
Console.WriteLine(String.Format("Time taken for simple add: {0}ms", watch.ElapsedMilliseconds));
uniqueSources.Clear();
watch.Restart();
foreach (LoggingMessage mess in bigCollection)
{
if (!uniqueSources.Contains(mess.Source))
uniqueSources.Add(mess.Source);
}
Console.WriteLine(String.Format("Time taken for conditional add: {0}ms", watch.ElapsedMilliseconds));
结果如下:
简单添加所需的时间:147毫秒
有条件添加所需的时间:125毫秒
所以至少对我的数据来说,检查存在并不会减慢速度,实际上会稍快一些。 不过它的差异很小。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.