初始化集合时,哈希集对内存有何作用?

2024-01-09

我偶然发现了以下问题。
我想要一个包含从 1 到 100.000.000 的所有数字的哈希集。 我尝试了以下代码:

var mySet = new HashSet<int>();
for (var k = 1; k <= 100000000; k++)
     mySet.Add(k);

该代码没有成功,因为我在 4900 万左右出现了内存溢出。这也相当慢并且内存增长过度。

然后我尝试了这个。

var mySet = Enumerable.Range(1, 100000000).ToHashSet();

其中 ToHashSet() 是以下代码:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

我再次遇到内存溢出,但我能够比之前的代码输入更多的数字。

有效的方法如下:

var tempList = new List<int>();
for (var k = 1; k <= 100000000; k++)
     tempList.Add(k);

var numbers = tempList.ToHashSet();

在我的系统上,填充 tempList 大约需要 800 毫秒,其中 Enumerable.Range() 仅需要 4 个刻度!

我确实需要那个 HashSet,否则查找值会花费很多时间(我需要它是 O(1)),如果我能以最快的方式做到这一点,那就太好了。

现在我的问题是:
为什么前两种方法会导致内存溢出,而第三种方法不会?

HashSet 在初始化时对内存有什么特殊的作用吗?

我的系统有 16GB 内存,所以当我收到溢出异常时我感到非常惊讶。


与其他集合类型一样,HashSet 会在您添加元素时根据需要自动增加其容量。当添加大量元素时,这将导致大量重新分配。

如果您使用带有IEnumerable<T>,它将检查是否IEnumerable<T>实际上是一个ICollection<T>,如果是,则将 HashSet 的容量初始化为集合的大小。

这就是你的第三个例子中发生的情况 - 你正在添加一个List<T>这也是一个ICollection<T>,因此您的 HashSet 的初始容量等于列表的大小,从而确保不需要重新分配。

如果您使用List<T>采用容量参数的构造函数,因为这将避免构建列表时的重新分配:

var noElements = 100000000;
var tempList = new List<int>(noElements); 
for (var k = 1; k <= noElements; k++) 
     tempList.Add(k); 

var numbers = tempList.ToHashSet(); 

至于你的系统内存;检查这是 32 位还是 64 位进程。 32 位进程最多有 2GB 可用内存(如果使用 /3GB 启动开关则为 3GB)。

与其他集合类型不同(例如List<T>, Dictionary<TKey,TValue>), HashSet<T>没有一个需要 a 的构造函数capacity参数设置初始容量。如果你想初始化一个HashSet<T>对于大量元素,最有效的方法可能是首先将元素添加到数组或List<T>具有适当的容量,然后将此数组或列表传递给HashSet<T>构造函数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

初始化集合时,哈希集对内存有何作用? 的相关文章

随机推荐