字符串分割:读取制表符分隔文件时出现“内存不足异常”

11

我在我的C#代码中使用string.split()来读取制表符分隔文件。 我遇到了如下示例代码中所述的“OutOfMemory异常”。

这里我想知道为什么对于16 MB大小的文件会出现问题?

这是正确的方法吗?

using (StreamReader reader = new StreamReader(_path))
{
  //...........Load the first line of the file................
  string headerLine = reader.ReadLine();

  MeterDataIPValueList objMeterDataList = new MeterDataIPValueList();
  string[] seperator = new string[1];   //used to sepreate lines of file

  seperator[0] = "\r\n";
  //.............Load Records of file into string array and remove all empty lines of file.................
  string[] line = reader.ReadToEnd().Split(seperator, StringSplitOptions.RemoveEmptyEntries);
  int noOfLines = line.Count();
  if (noOfLines == 0)
  {
    mFileValidationErrors.Append(ConstMsgStrings.headerOnly + Environment.NewLine);
  }
  //...............If file contains records also with header line..............
  else
  {
    string[] headers = headerLine.Split('\t');
    int noOfColumns = headers.Count();

    //.........Create table structure.............
    objValidateRecordsTable.Columns.Add("SerialNo");
    objValidateRecordsTable.Columns.Add("SurveyDate");
    objValidateRecordsTable.Columns.Add("Interval");
    objValidateRecordsTable.Columns.Add("Status");
    objValidateRecordsTable.Columns.Add("Consumption");

    //........Fill objValidateRecordsTable table by string array contents ............

    int recordNumber;  // used for log
    #region ..............Fill objValidateRecordsTable.....................
    seperator[0] = "\t";
    for (int lineNo = 0; lineNo < noOfLines; lineNo++)
    {
      recordNumber = lineNo + 1;
      **string[] recordFields = line[lineNo].Split(seperator, StringSplitOptions.RemoveEmptyEntries);** // Showing me error when we  split columns
      if (recordFields.Count() == noOfColumns)
      {
        //Do processing
      }

4
顺便提一下,Eric Lippert在他的博客中对OutOfMemoryExceptions(内存不足异常)进行了很好的阐述。http://blogs.msdn.com/ericlippert/archive/2009/06/08/out-of-memory-does-not-refer-to-physical-memory.aspx - Ray Booysen
这是在紧凑框架上吗(即Windows Mobile)? - MusiGenesis
5个回答

13

Split函数的实现较差,当对大字符串应用此函数时,会出现严重的性能问题。请参考此文了解Split函数的内存需求:

当您对一个包含1355049个由逗号分隔的、每个字符串长度为16个字符的字符串进行拆分时,会发生什么情况?该字符串总长度为25745930。

  1. 指向字符串对象的数组:连续的虚拟地址空间大小为4(地址指针)* 1355049 = 5420196(数组大小)+ 16(用于簿记)= 5420212。

  2. 非连续的虚拟地址空间用于1355049个字符串,每个字符串占用54个字节。这并不意味着所有这130万个字符串都将散布在堆上,但它们不会被分配到LOH上。GC将它们分配到Gen0堆上的一组中。

  3. Split函数将创建一个大小为25745930的System.Int32[]内部数组,消耗(102983736字节)约98MB的LOH内存,这是非常昂贵的。


11

尝试直接逐行读取文件而不是先将整个文件读入数组"reader.ReadToEnd()"中。

  using (StreamReader sr = new StreamReader(this._path))
        {
            string line = "";
            while(( line= sr.ReadLine()) != null)
            {
                string[] cells = line.Split(new string[] { "\t" }, StringSplitOptions.None);
                if (cells.Length > 0)
                {

                }
            }
        }

5

我使用自己的代码。它已经通过了10个单元测试的检验。

public static class StringExtensions
{

    // the string.Split() method from .NET tend to run out of memory on 80 Mb strings. 
    // this has been reported several places online. 
    // This version is fast and memory efficient and return no empty lines. 
    public static List<string> LowMemSplit(this string s, string seperator)
    {
        List<string> list = new List<string>();
        int lastPos = 0;
        int pos = s.IndexOf(seperator);
        while (pos > -1)
        {
            while(pos == lastPos)
            {
                lastPos += seperator.Length;
                pos = s.IndexOf(seperator, lastPos);
                if (pos == -1)
                    return list;
            }

            string tmp = s.Substring(lastPos, pos - lastPos);
            if(tmp.Trim().Length > 0)
                list.Add(tmp);
            lastPos = pos + seperator.Length;
            pos = s.IndexOf(seperator, lastPos);
        }

        if (lastPos < s.Length)
        {
            string tmp = s.Substring(lastPos, s.Length - lastPos);
            if (tmp.Trim().Length > 0)
                list.Add(tmp);
        }

        return list;
    }
}

1
太棒了!非常顺利!谢谢! - DIVIJ

4
我建议您如果可以的话逐行阅读,但有时不需要按新行分割。因此,您始终可以编写自己的内存高效分割代码。这对我解决了问题。
    private static IEnumerable<string> CustomSplit(string newtext, char splitChar)
    {
        var result = new List<string>();
        var sb = new StringBuilder();
        foreach (var c in newtext)
        {
            if (c == splitChar)
            {
                if (sb.Length > 0)
                {
                    result.Add(sb.ToString());
                    sb.Clear();
                }
                continue;
            }
            sb.Append(c);
        }
        if (sb.Length > 0)
        {
            result.Add(sb.ToString());
        }
        return result;
    }

1
尝试逐行读取文件,而不是将整个内容分割。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接