我使用Linq的Except语句耗尽内存。
示例:
var numbers = Enumerable.Range(1, 10000000);
int i=0;
while (numbers.Any())
{
numbers = numbers.Except(new List<int> {i++});
}
我对该方法进行了反编译,这样做也是如此,并且还给出了内存不足的异常。
var numbers = Enumerable.Range(1, 10000000);
int i=0;
while (numbers.Any())
{
numbers = CustomExcept(numbers, new List<int>{i++});
}
private IEnumerable<int> CustomExcept(IEnumerable<int> numbers, IEnumerable<int> exceptionList)
{
HashSet<int> set = new HashSet<int>();
foreach (var i in exceptionList)
{
set.Add(i);
}
foreach (var i in numbers)
{
if (set.Add(i))
{
yield return i;
}
}
}
所以我的问题是:为什么会抛出一个内存不足的异常?
我希望垃圾收集器能够清理未使用的HashSet。
答案 0 :(得分:5)
当你i==10
numbers.Any()
时,那个&#34;数字&#34;不是从10到1000万的数字列表,而是:
Enumerable.Range(1, 10000000)
.Except({0})
.Except({1})
.Except({2})
// (etc)
.Except({9})
所有这些&#34; Excepts&#34;拥有自己非常活跃的hashset。所以垃圾收集没什么。
您必须添加一些.ToList()
才能真正执行这些Excepts并为垃圾收集器提供一些机会。
答案 1 :(得分:1)
我同意,这是一个非常令人惊讶的行为。在这种情况下,我也不会期望Except
内存不足。
但如果你看reference source for the Enumerable class,你会看到原因。除了(通过ExceptIterator辅助方法):
Set<T>
所以只是只是做一个&#34;除了#34;它还隐含地做了一个&#34; distinct&#34;。并且为了做到这一点&#34; distinct&#34;,它也将第一个列表的所有元素添加到集合中...所以使用巨大的第一个列表,是的,你会消耗大量的内存。
我希望它能做到&#34;包含&#34;在第二个循环中,而不是&#34;添加&#34;。我也没有在documentation中看到这种行为。我看到的最接近的是描述:
使用默认的相等比较器来比较值,生成两个序列的集合差异。
如果它将其参数视为集合,那么删除重复项是有意义的,因为它是什么集合。但这不是我从方法名称预测的东西!
无论如何,您最好的选择可能是摆脱Except
,而是将Last
捕获到变量中,然后执行Where(value => value != last)
。