【问题标题】:Memory Leak(?) with StreamReader带有 StreamReader 的内存泄漏(?)
【发布时间】:2012-10-02 11:16:48
【问题描述】:

我有几个非常大的文件,每个 500MB++ 大小,包含整数值(实际上它有点复杂),我正在循环读取这些文件并计算所有文件的最大值。由于某种原因,内存在处理过程中不断增长,看起来 GC 从未释放由lines 的先前实例获取的内存。

我无法流式传输数据,必须为每个文件使用GetFileLines。假设为一个文件存储lines 所需的实际内存量是500MB,为什么在处理了10 个文件后使用5GB 的RAM?最终它在 15 个文件后因内存不足异常而崩溃。

计算:

   int max = int.MinValue;

   for (int i = 0; i < 10; i++)
   {
      IEnumerable<string> lines = Db.GetFileLines(i);

      max = Math.Max(max, lines.Max(t=>int.Parse(t)));
   }

GetFileLines 代码:

   public static List<string> GetFileLines(int i)
   {
      string path = GetPath(i);

      //
      List<string> lines = new List<string>();
      string line;

      using (StreamReader reader = File.OpenText(path))
      {
         while ((line = reader.ReadLine()) != null)
         {
            lines.Add(line);
         }

         reader.Close();
         reader.Dispose(); // should I bother?
      }

      return lines;
   }

【问题讨论】:

  • 你调用lines.Clear()方法吗?
  • 10 x 500 Mb = 5 GB。您将所有文件内容保留在列表 List 行 中。与 ReadAllLines() 的性能相同
  • 如果您有指向行的指针,垃圾收集器将不会清除内存。处理完行后,您必须 clear() 集合
  • 不是这样,因为每个文件总是有一个新的列表实例,GC应该清理最后一个
  • 你正在返回列表!你用它做什么?

标签: c# streamreader


【解决方案1】:

对于非常大的文件,方法ReadLines 将是最合适的,因为它是延迟执行,它不会将所有行加载到内存中并且使用简单:

  Math.Max(max, File.ReadLines(path).Max(line => int.Parse(line)));

更多信息:

http://msdn.microsoft.com/en-us/library/dd383503.aspx

编辑:

ReadLines 在幕后是这样实现的:

    public static IEnumerable<string> ReadLines(string fileName)
    {
        string line;
        using (var reader = File.OpenText(fileName))
        {
            while ((line = reader.ReadLine()) != null)
                yield return line;
        }
    }

另外,当您有多个文件时,建议使用并行处理来提高性能

【讨论】:

  • 抱歉处理逻辑比较复杂,这个例子只是说明了它与读取过程完全解耦。
  • @user1514042:没关系,你可以用它和LINQ一起处理非常大的文件
  • 实际行数为500K++时会不会影响性能?
  • 非常优雅的解决方案朋友。
  • @user1514042,LINQ 只是为你封装了循环......对吗?
【解决方案2】:

您可能会崩溃,因为您在处理完解析结果后将它们的引用保存在内存中(您显示的代码不会这样做,但您运行的代码是否相同?)。 StreamReader 中不太可能存在这样的错误。

您确定必须一次读取内存中的所有文件吗?很有可能使用可枚举的行序列作为IEnumerable&lt;string&gt;,而不是预先加载List&lt;string&gt;。至少在这段代码中没有什么禁止这样做的。

最后,Close 和 Dispose 调用是多余的; using 会自动处理。

【讨论】:

  • 好吧,我只使用值类型,它们还能持有引用吗?
  • 当然可以。如果您能以某种方式访问​​该列表,则说明有人持有对它的引用。
  • 没错,但它每次都会被替换,如果我不高兴最后 500MB 没有被清除,你的观点是正确的,但我有一个不同的问题。
  • @user1514042:如果您的内存不足,那么某些地方的引用没有被清除。就是这么简单。
  • @user1514042,小心你的演讲朋友。您肯定不会以您认为的方式管理内存,否则您不会耗尽内存。请记住,IEnumerable&lt;string&gt; lines = Db.GetFileLines(i); 这一行每次都会复制列表,但仅替换了之前的引用,因此之前的 List&lt;string&gt; 仍然存在于堆中。
【解决方案3】:

为什么不按如下方式实现:

int max = Int32.MinValue;
using(var reader = File.OpenText(path)) 
{
    while ((line = reader.ReadLine()) != null)
    {
         int current;
         if (Int32.TryParse(line, out current))
             max = Math.Max(max, current);
     }    
}

【讨论】:

    【解决方案4】:

    您正在将整个文件读入内存(列表行)

    我猜你可以一次读一行并保持最高的数字?

    它将为您节省大量内存。

    【讨论】:

    • 每行需要 0.5 秒来处理,这就是为什么读取它们然后处理要快得多的原因。这样做我们收获很多,性能测试证实了这一点。
    【解决方案5】:

    您似乎总是将整个文件加载到内存中。同时,您也在为文件的每一行创建托管对象(List)。

    您的内存使用量没有理由增加。

    还请发布其余代码,我怀疑您是否在某个地方引用了这个正在使用的列表,因此它没有被处理。

    【讨论】:

      【解决方案6】:

      好的,如果您想要一个可以一次读取整个文件的解决方案,因为您确定需要提高性能,那么让我们这样做,这样您就可以没有内存问题。

      public static int GetMaxForFile(int i) 
      { 
          string path = GetPath(i); 
      
          var lines = new List<string>(File.ReadAllLines(path));
      
          // you MUST perform all of your processing here ... you have to let go
          // of the List<string> variable ...
          int max = Math.Max(max, lines.Max(t=>int.Parse(t)));
      
          // this may be redundant, but it will cause GC to clean up immediately
          lines.Clear();
          lines = null;
      
          return max;
      } 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-11
        • 1970-01-01
        • 1970-01-01
        • 2016-05-14
        • 2012-04-06
        • 1970-01-01
        相关资源
        最近更新 更多