C# 字符串数组为大型多行条目抛出OutOfMemoryException

C# 字符串数组为大型多行条目抛出OutOfMemoryException,c#,winforms,string,out-of-memory,C#,Winforms,String,Out Of Memory,在Windows Forms C#应用程序中,我有一个文本框,用户可以在其中粘贴日志数据,并对其进行排序。我需要单独检查每一行,所以我用新行分割输入,但是如果有很多行,大于100000左右,它会抛出OutOfMemoryException 我的代码如下所示: StringSplitOptions splitOptions = new StringSplitOptions(); if(removeEmptyLines_CB.Checked) splitOptions = StringSpl

在Windows Forms C#应用程序中,我有一个文本框,用户可以在其中粘贴日志数据,并对其进行排序。我需要单独检查每一行,所以我用新行分割输入,但是如果有很多行,大于100000左右,它会抛出OutOfMemoryException

我的代码如下所示:

StringSplitOptions splitOptions = new StringSplitOptions();
if(removeEmptyLines_CB.Checked)
    splitOptions = StringSplitOptions.RemoveEmptyEntries;
else
    splitOptions = StringSplitOptions.None;

List<string> outputLines = new List<string>();

foreach(string line in input_TB.Text.Split(new string[] { "\r\n", "\n" }, splitOptions))
{
    if(line.Contains(inputCompare_TB.Text))
        outputLines.Add(line);
}
output_TB.Text = string.Join(Environment.NewLine, outputLines);

Split
必须复制原始文本的内存需求,加上每行
string
对象的开销。如果这会导致内存问题,处理输入的可靠方法是一次解析一行。

我想对大型文本文件执行此操作的唯一方法是手动打开文件并使用
StreamReader
。这是一个示例。通过一次为每一行创建一个字符串,可以避免为所有行和数组创建字符串:

var eol = new[] { '\r', '\n' };

var pos = 0;
while (pos < input.Length)
{
    var i = input.IndexOfAny(eol, pos);
    if (i < 0)
    {
        i = input.Length;
    }
    if (i != pos)
    {
        var line = input.Substring(pos, i - pos);

        // process line
    }
    pos = i + 1;
}
var-eol=new[]{'\r','\n'};
var-pos=0;
while(位置<输入长度)
{
变量i=输入指数(eol,pos);
if(i<0)
{
i=输入长度;
}
如果(i!=pos)
{
var行=输入子字符串(pos,i-pos);
//生产线
}
pos=i+1;
}
另一方面,我们认为问题的关键在于“拆分”方法的实现很差。读一读,得出你的结论


正如Attila所说,您必须逐行分析。

更好的方法是一次提取并处理一行,然后使用
StringBuilder
创建结果:

StringBuilder outputTxt = new StringBuilder();
string txt = input_TB.Text;
int txtIndex = 0;
while (txtIndex < txt.Length) {
  int startLineIndex = txtIndex;
GetMore:
  while (txtIndex < txt.Length && txt[txtIndex] != '\r'  && txt[txtIndex] != '\n')) {
    txtIndex++;
  }
  if (txtIndex < txt.Length && txt[txtIndex] == '\r' && (txtIndex == txt.Length-1 || txt[txtIndex+1] != '\n') {
    txtIndex++;
    goto GetMore; 
  }
  string line = txt.Substring(startLineIndex, txtIndex-startLineIndex);
  if (line.Contains(inputCompare_TB.Text)) {
    if (outputTxt.Length > 0)
      outputTxt.Append(Environment.NewLine);
    outputTxt.Append(line); 
  }
  txtIndex++;
} 
output_TB.Text = outputTxt.ToString(); 

谢谢,看看我的更新,让我知道这是不是你的意思。我会尽快将此标记为回答,我只想看到其他一些想法。再次感谢!我甚至不知道你可以在c#中使用goto语句,不要认为我从孩提时代就开始使用goto语句了,因为我在玩pascal和basic,很有趣。这似乎太复杂了,请看一下我对问题的更新。我在回答的末尾添加了一条注释-您的更新比较干净,但没有将
\r\n
\n
作为结束行处理。如果你能消除这个问题,那就好了-我仍然建议使用
StringBuilder
,避免创建一个(大的?)中间字符串列表。是的,
goto
在C#中是可能的,我会谨慎地使用它们,就像在本例中一样。StringReader将\r\n或\r\n作为换行符处理。在我的更新代码中,“while”((line=reader.ReadLine())!=null)“创建一组字符串,还是继续重复使用相同的字符串?
StringBuilder outputTxt = new StringBuilder();
string txt = input_TB.Text;
int txtIndex = 0;
while (txtIndex < txt.Length) {
  int startLineIndex = txtIndex;
GetMore:
  while (txtIndex < txt.Length && txt[txtIndex] != '\r'  && txt[txtIndex] != '\n')) {
    txtIndex++;
  }
  if (txtIndex < txt.Length && txt[txtIndex] == '\r' && (txtIndex == txt.Length-1 || txt[txtIndex+1] != '\n') {
    txtIndex++;
    goto GetMore; 
  }
  string line = txt.Substring(startLineIndex, txtIndex-startLineIndex);
  if (line.Contains(inputCompare_TB.Text)) {
    if (outputTxt.Length > 0)
      outputTxt.Append(Environment.NewLine);
    outputTxt.Append(line); 
  }
  txtIndex++;
} 
output_TB.Text = outputTxt.ToString(); 
StringReader reader = new StringReader(input_TB.Text); 
StringBuilder outputTxt = new StringBuilder();
string compareTxt = inputCompare_TB.Text;
string line; 
while((line = reader.ReadLine()) != null) { 
  if (line.Contains(compareTxt)) {
    if (outputTxt.Length > 0)
      outputTxt.Append(Environment.NewLine);
    outputTxt.Append(line); 
  }
} 
output_TB.Text = outputTxt.ToString();