【问题标题】:Replacing text in a huge string without memory leak在没有内存泄漏的情况下替换巨大字符串中的文本
【发布时间】:2012-07-31 13:19:50
【问题描述】:

我目前正在处理一个必须连续生成大约 16000 封电子邮件的批次(简报)。

不管它是不是垃圾邮件,我的问题是关于我如何生成这些电子邮件。

消息中的某些字段必须替换为自定义值(日期、用户名等)。

出于某些截止日期和代码可重用性的原因,我的模板是一个 HTML 文件,其中包含一些“_FIELDNAME”字段,可以通过正则表达式轻松发现:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
...
<body>
<p>Hi _NAME, _DATE newsletter.</p>
</body>
...

该文件大约有 1000 行,因此在加载时它是一个相当大的字符串。

首先,我在一个字符串中加载一次 HTML 文件模板:

string template = File.ReadAllText(@"Template/newsletter.html");

替换函数如下所示:

return new StringBuilder(template)
.Replace("_DATE", profileConfig.SelectedMonth.ToString("MMMM yyyy"))
.Replace("_NAME", profileConfig.Name)
.ToString();

问题是内存消耗在每次迭代中都会略有增加。 1000 次迭代大约 50MB,这是由于我的替换功能(我试图评论它,内存泄漏消失了)。

如何在我的 16000 次迭代中替换模板中的许多字段 (~50) 而不会溢出内存? 我尝试了几件事,比如使用正则表达式(但它使用字符串)或临时文件,但都不满意。

提前感谢您的帮助。

【问题讨论】:

  • 这里为什么会出现内存泄漏?你怎么知道 GC 根本没有看到收集垃圾的必要性呢?
  • 50Mb 1000 次迭代似乎并不多。这表明您将在 16000 次迭代结束时达到 800Mb 左右,这是假设您对泄漏正确(我认为您不是)。为什么会出现这个问题?
  • @MennanKara:这个StringBuilder只有一个引用,当方法返回时它会自动“丢失”。没有必要做任何事情。
  • @Jon:链接页面上的答案说的完全一样,试图解决这个问题没有意义。

标签: c# .net memory-leaks replace


【解决方案1】:

如果你可以用{0}{1}等替换你的_DATE_NAME等,你可以试试string.Format()

模板将变为:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
...
<body>
<p>Hi {0}, {1} newsletter.</p>
</body>
...

代码如下所示:

return string.Format(template, 
        profileConfig.SelectedMonth.ToString("MMMM yyyy"), 
        profileConfig.Name
    );

您实际上根本不需要通过StringBuilder。如果您使用File.ReadAllLines() 并且只在包含标记的行中交换值,您将大大受益于速度(并且可能在资源使用方面)。

更新 为了强制使用string.Format(string format, params object[] args) 重载,您可能必须将所有参数放入一个集合中。

以下内容应该使该解决方案适合您(我测试了多达 1000 个参数,并且它的工作速度都非常快)。

List<string> tokenValues = new List<string> 
{ 
    profileConfig.SelectedMonth.ToString("MMMM yyyy"), 
    profileConfig.Name, 
    <follow with your other values>
};
return string.Format(template, tokenValues.ToArray()); //.ToArray() is mandatory

【讨论】:

  • 非常感谢,我将尝试这 2 个解决方案,并会给你结果。
  • string.Format 解决方案不起作用,参数太多(超过 50 个),格式化程序失败。也许它不喜欢 HTML。对于您的第二个想法,您将如何替换令牌? ReadAllLines 返回一个字符串[],所以我需要遍历每一行并进行替换?
  • 有一种方法可以用许多参数“欺骗”string.Format()。我将尝试使用 50 多个参数,如果可行,我将编辑答案。
【解决方案2】:
    var patterns = new Dictionary<string, string>();
    patterns["_Date"] = profileConfig.SelectedMonth.ToString("MMMM yyyy");
    patterns["_Name"] = profileConfig.Name;

    var builder = new StringBuilder(template.Length);
    for (var i = 0; i < template.Length;)
    {
      var pattern = CompareAndFindPattern(template, i, patterns);
      if (pattern != null)
      {
        builder.Append(pattern.Value.Value);
        i += pattern.Value.Key.Length;
      }
      else
      {
        builder.Append(template[i]);
        i++;
      }
    }

  static KeyValuePair<string, string>? CompareAndFindPattern(string template, int index, Dictionary<string, string> patterns)
  {
    foreach (var pattern in patterns)
    {
      if (string.Compare(template, index, pattern.Key, 0, pattern.Key.Length) == 0)
        return pattern;
    }
    return null;
  }

【讨论】:

    【解决方案3】:

    在尝试了很多解决方案后,我最终决定从头开始重新启动我的批处理。

    我现在使用适当的 XSLT 文件从 XML 配置生成 HTML。

    内存消耗仍会随着时间的推移而增加,但现在变慢了。我猜垃圾收集器不想收集,因为我的电脑有 6GB RAM,没有其他大型进程可以运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-19
      • 2010-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-07
      相关资源
      最近更新 更多