更新大列表时抛出OutOfMemoryException异常
本文关键字:OutOfMemoryException 异常 列表 更新 | 更新日期: 2023-09-27 18:12:54
我有一个大列表,如果需要,我想覆盖一个值。为了做到这一点,我创建了列表的两个子集,这似乎给了我一个OutOfMemoryException
。下面是我的代码片段:
if (ownRG != "")
{
List<string> maclist = ownRG.Split(',').ToList();
List<IVFile> temp = powlist.Where(a => maclist.Contains(a.Machine)).ToList();
powlist = powlist.Where(a => !maclist.Contains(a.Machine)).ToList(); // OOME Here
temp.ForEach(a => { a.ReportingGroup = ownRG; });
powlist.AddRange(temp);
}
本质上,我将列表分成需要更新的部分和不需要更新的部分,然后执行更新并将列表重新组合在一起。这对于较小的列表很好,但是对于较大的列表,在if
的第三行上出现OutOfMemoryException
就会中断。我能让它更有效率吗?
注意powlist
为大列表(>1m)项。maclist
只有1到10项,但即使只有1项,这也打破了。
解决您的问题
下面是如何使用我的答案中的枚举器代码重新排列代码:
if (!string.IsNullOrEmpty(ownRG))
{
var maclist = new CommaSeparatedStringEnumerable(str);
var temp = powlist.Where(a => maclist.Contains(a.Machine));
foreach (var p in temp)
{
p.ReportingGroup = ownRG;
}
}
- 你不应该在你的代码中使用
ToList
。 - 你不需要从
powlist
中删除temp
的三个内容(无论如何你都要重新添加它们)
流式传输一个大的逗号分隔字符串
您可以手动遍历列表,而不是做您现在做的事情,通过查找,
字符并记住最后找到的字符和前一个字符的位置。这肯定会使你的应用程序工作,因为这样它就不需要一次将整个集合存储在内存中。
代码示例:
var str = "aaa,bbb,ccc";
var previousComma = -1;
var currentComma = 0;
for (; (currentComma = str.IndexOf(',', previousComma + 1)) != -1; previousComma = currentComma)
{
var currentItem = str.Substring(previousComma + 1, currentComma - previousComma - 1);
Console.WriteLine(currentItem);
}
var lastItem = str.Substring(previousComma + 1);
Console.WriteLine(lastItem);
自定义迭代器
如果你想以一种奇特的方式"正确"地做到这一点,你甚至可以编写一个自定义的枚举器:
public class CommaSeparatedStringEnumerator : IEnumerator<string>
{
int previousComma = -1;
int currentComma = -1;
string bigString = null;
bool atEnd = false;
public CommaSeparatedStringEnumerator(string s)
{
if (s == null)
throw new ArgumentNullException("s");
bigString = s;
this.Reset();
}
public string Current { get; private set; }
public void Dispose() { /* No need to do anything here */ }
object IEnumerator.Current { get { return this.Current; } }
public bool MoveNext()
{
if (atEnd)
return false;
atEnd = (currentComma = bigString.IndexOf(',', previousComma + 1)) == -1;
if (!atEnd)
Current = bigString.Substring(previousComma + 1, currentComma - previousComma - 1);
else
Current = bigString.Substring(previousComma + 1);
previousComma = currentComma;
return true;
}
public void Reset()
{
previousComma = -1;
currentComma = -1;
atEnd = false;
this.Current = null;
}
}
public class CommaSeparatedStringEnumerable : IEnumerable<string>
{
string bigString = null;
public CommaSeparatedStringEnumerable(string s)
{
if (s == null)
throw new ArgumentNullException("s");
bigString = s;
}
public IEnumerator<string> GetEnumerator()
{
return new CommaSeparatedStringEnumerator(bigString);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
然后你可以像这样遍历它:
var str = "aaa,bbb,ccc";
var enumerable = new CommaSeparatedStringEnumerable(str);
foreach (var item in enumerable)
{
Console.WriteLine(item);
}
其他想法
我能让它更有效率吗?
是的,你可以。我建议使用更有效的数据格式(您可以根据需要查看数据库或XML、JSON等)。如果您真的想使用逗号分隔的项,请参阅上面的代码示例。
没有必要从powlist
创建一堆子列表并重建它。只需循环powlist
并相应地更新ReportingGroup
属性。
var maclist = new HashSet<string>( ownRG.Split(',') );
foreach( var item in powlist) {
if( maclist.Contains( item.Machine ) ){
item.ReportingGroup = ownRG;
}
}
由于这改变了powlist
的位置,您将不会分配任何额外的内存,也不应该运行到OutOfMemoryException
。
在循环中查找下一个',' char。取','和前一个','位置之间的子字符串。在循环结束时保存对前一个','位置的引用(最初设置为0)。因此您可以逐个解析项,而不是一次解析所有项。
您可以尝试循环列表中的项,但这会增加处理时间。
foreach(var item in powlist)
{
//do your opeartions
}