【问题标题】:C# Error: OutOfMemoryException - Reading a large text file and replacing from dictionaryC# 错误:OutOfMemoryException - 读取大文本文件并从字典中替换
【发布时间】:2017-07-06 09:19:17
【问题描述】:

我是 C# 和面向对象编程的新手。我有一个解析文本文件的应用程序。

应用程序的目标是读取提供的文本文件的内容并替换匹配的值。

当提供大约 800 MB 到 1.2GB 的文件作为输入时,应用程序崩溃并出现错误 System.OutofMemoryException。

在研究过程中,我遇到了几个建议将 Target Platform: 更改为 x64 的答案。

更改目标平台后也存在同样的问题。

以下是代码:

// Reading the text file
                var _data = string.Empty;
                using (StreamReader sr = new StreamReader(logF))
                {
                    _data = sr.ReadToEnd();
                    sr.Dispose();
                    sr.Close();
                }

                foreach (var replacement in replacements)
                {
                    _data = _data.Replace(replacement.Key, replacement.Value);
                }


                //Writing The text File
                using (StreamWriter sw = new StreamWriter(logF))
                {
                    sw.WriteLine(_data);
                    sw.Dispose();
                    sw.Close();
                } 

错误指向

_data = sr.ReadToEnd();

replacements 是一本字典。 Key 包含原始单词,Value 包含要替换的单词。

Key 元素被 KeyValuePair 的 Value 元素替换。

遵循的方法是读取文件,替换和写入。

我尝试使用 StringBuilder 代替字符串,但应用程序崩溃了。

可以通过一次读取一行文件、替换和写入来克服这个问题吗?做同样的事情的有效和更快的方法是什么。

更新:系统内存为 8 GB,在监控性能时,内存使用率达到 100%。

@Tim Schmelter 的回答效果很好。

但是,内存利用率飙升超过 90%。这可能是由于以下代码:

            String[] arrayofLine = File.ReadAllLines(logF);
            // Generating Replacement Information
            Dictionary<int, string> _replacementInfo = new Dictionary<int, string>();
            for (int i = 0; i < arrayofLine.Length; i++)
            {
                foreach (var replacement in replacements.Keys)
                {
                    if (arrayofLine[i].Contains(replacement))
                    {
                        arrayofLine[i] = arrayofLine[i].Replace(replacement, masking[replacement]);
                        if (_replacementInfo.ContainsKey(i + 1))
                        {
                            _replacementInfo[i + 1] = _replacementInfo[i + 1] + "|" + replacement;
                        }
                        else
                        {
                            _replacementInfo.Add(i + 1, replacement);
                        }
                    }
                }
            }

//Creating Replacement Information
                StringBuilder sb = new StringBuilder();
                foreach (var Replacement in _replacementInfo)
                {
                    foreach (var replacement in Replacement.Value.Split('|'))
                    {
                        sb.AppendLine(string.Format("Line {0}: {1} ---> \t\t{2}", Replacement.Key, replacement, masking[replacement]));
                    }
                }

                // Writing the replacement information
                if (sb.Length!=0)
                { 
                using (StreamWriter swh = new StreamWriter(logF_Rep.txt))
                {
                    swh.WriteLine(sb.ToString());
                    swh.Dispose();
                    swh.Close();
                }
                }
                sb.Clear();

它找到进行替换的行号。是否可以使用 Tim 的代码来捕获,以避免将数据多次加载到内存中。

【问题讨论】:

  • 请更新您的帖子以包含日志文件的前几行。你的机器有多少内存?
  • 那为什么不逐行阅读呢?
  • 逐行读取,现在您将整个文件数据放入内存中,如果文件大小大于机器内存,则会导致失败
  • 更新帖子

标签: c# dictionary system.io.file


【解决方案1】:

如果您有非常大的文件,您应该尝试MemoryMappedFile,它是为此目的而设计的(文件 > 1GB),可以将文件的“窗口”读入内存。但它并不容易使用。

一个简单的优化是逐行读取和替换

int lineNumber = 0;
var _replacementInfo = new Dictionary<int, List<string>>();

using (StreamReader sr = new StreamReader(logF))
{
    using (StreamWriter sw = new StreamWriter(logF_Temp))
    {
        while (!sr.EndOfStream)
        {
            string line = sr.ReadLine();
            lineNumber++;
            foreach (var kv in replacements)
            {
                bool contains = line.Contains(kv.Key);
                if (contains)
                {
                    List<string> lineReplaceList;
                    if (!_replacementInfo.TryGetValue(lineNumber, out lineReplaceList))
                        lineReplaceList = new List<string>();
                    lineReplaceList.Add(kv.Key);
                    _replacementInfo[lineNumber] = lineReplaceList;

                    line = line.Replace(kv.Key, kv.Value);
                }
            }
            sw.WriteLine(line);
        }
    }
}

如果你想覆盖旧的,最后你可以使用File.Copy(logF_Temp, logF, true);。

【讨论】:

  • 虽然你没有使用vLineNumber
  • 关于MemoryMappedFile 我和他们一起工作,ayende 的帖子对我帮助很大ayende.com/blog/search?q=Memory+Mapped+Files
  • @Tim Schmelter 代码有效。是否可以使用相同的代码捕获替换信息?进行替换的行号?我已经更新了帖子。
  • @Tango:您更新的代码包含String[] arrayofLine = File.ReadAllLines(logF);。这会将整个文件读入内存。你为什么不使用我的代码,它只读取一行或File.ReadLines,它也只读取一行。
  • @Tango:更改了我的代码以添加行号信息
【解决方案2】:

逐行读取文件并将更改的行附加到其他文件。最后用新文件替换源文件(是否创建备份)。

var tmpFile = Path.GetTempFileName();
using (StreamReader sr = new StreamReader(logF))
{
    using (StreamWriter sw = new StreamWriter(tmpFile))
    {
        string line;
        while ((line = sr.ReadLine()) != null)
        {
            foreach (var replacement in replacements)
                line = line.Replace(replacement.Key, replacement.Value);

            sw.WriteLine(line);
        }
    }
}
File.Replace(tmpFile, logF, null);// you can pass backup file name instead on null if you want a backup of logF file

【讨论】:

    【解决方案3】:

    每当应用程序尝试分配内存以执行操作但失败时,就会引发 OutOfMemoryException。根据 Microsoft 的文档,以下操作可能会引发 OutOfMemoryException:

    装箱(即将值类型包装在对象中) 创建数组 创建对象 如果您尝试创建无限数量的对象,那么假设您迟早会耗尽内存是非常合理的。

    (注意:不要忘记垃圾收集器。根据正在创建的对象的生命周期,如果确定它们不再使用,它​​将删除其中的一些。)

    我怀疑是这条线:

      foreach (var replacement in replacements)
                    {
                        _data = _data.Replace(replacement.Key, replacement.Value);
                    }
    

    你迟早会耗尽内存。你数过它循环了多少次吗?

    试试

    • 增加可用内存。
    • 减少您检索的数据量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-07
      • 1970-01-01
      • 2014-06-03
      • 2013-03-14
      • 1970-01-01
      • 2016-08-04
      • 2021-07-20
      相关资源
      最近更新 更多