【问题标题】:Regex replacements inside a StringBuilderStringBuilder 中的正则表达式替换
【发布时间】:2010-08-17 16:43:25
【问题描述】:

我正在将文本文件的内容写入 StringBuilder,然后我想使用正则表达式对 StringBuilder 中包含的文本执行一些查找/替换操作。

我遇到了一个问题,因为 StringBuilder 替换函数不能接受正则表达式参数。

我可以在普通字符串上使用 Regex.Replace,但我的印象是这样做效率低下,因为 .net 字符串是不可变的,因此需要在内存中创建两个字符串副本。

一旦我更新了文本,我打算将它写回原始文件。

解决我的问题最好和最有效的方法是什么?

编辑

除了下面的答案之外,我还发现了以下问题,这些问题也对我的问题有所启发 -

【问题讨论】:

    标签: c# regex stringbuilder


    【解决方案1】:

    最好和最有效的解决方案您的时间是首先尝试最简单的方法:忘记StringBuilder,而只使用Regex.Replace。然后找出它有多慢 - 它可能已经足够好了。不要忘记在编译和非编译模式下尝试正则表达式。

    如果这不够足够快,请考虑使用StringBuilder 来替换您可以简单表达的任何替换,然后使用Regex.Replace 来替换其余部分。您可能还想考虑尝试组合替换,减少使用的正则表达式(以及中间字符串)的数量。

    【讨论】:

    • 我很惊讶我没有想到这一点:实际运行它看看,而不是猜测速度会是多少。我已相应地删除了我的推测性答案。
    • 如果 Regex.Replace 足够快,我是否应该完全关注内存管理?我是否因担心创建多个字符串的内存开销而过度分析/优化事物?
    • 这与其说是一个建议,不如说是一个答案。问题是如何使正则表达式与 stringbuilder 一起工作,答案是它们不兼容,除非您编写自己的实现。为什么会这样,我不知道。
    【解决方案2】:

    您有 3 个选项:

    1. 使用其他人在此处推荐的字符串以低效的方式执行此操作。

    2. Regex 对象使用.Matches() 调用,并模拟.Replace() 的工作方式(参见#3)。

    3. 调整 Regex 的 Mono 实现以构建一个接受 StringBuilderRegex。几乎所有的工作都已经在 Mono 中为您完成了,但是需要时间来找出使其在自己的库中工作的部分。 Mono 的 Regex 利用了 Novell 2002 年对 Regex 的 JVM 实现,这很奇怪。

    扩展上述内容:

    2。模拟替换()

    您可以通过调用.Matches() 来模仿LTRReplace 的行为,跟踪您在原始字符串中的位置并循环:

    var matches = regex.Matches(original);
    var sb = new StringBuilder(original.Length);
    int pos = 0; // position in original string
    foreach(var match in matches)
    {
        // Append the portion of the original we skipped
        sb.Append(original.Substring(pos, match.Index));
        pos = match.Index;
    
        // Make any operations you like on the match result, like your own custom Replace, or even run another Regex
    
        pos += match.Value.Length;
    }
    sb.Append(original.Substring(pos, original.Length - 1));
    

    但是,这只会为您节省一些字符串 - Mono 方法是唯一真正彻底消除字符串的方法。

    3。单声道

    这个答案自 2014 年以来一直没有出现,我从未在 cmets 或搜索中看到基于 StringBuilder 的 Regex 土地。因此,为了让事情顺利进行,我从 Mono 中提取了 Regex impl 并将其放在这里:

    https://github.com/brass9/RegexStringBuilder

    然后我创建了一个接口 IString 以允许更松散地传递输入和输出 - stringStringBuilderchar[] 分别包装在一个实现 IString 的类中。

    结果并不快 - Microsoft 高度优化的代码运行 10,000 次简单替换,比此代码快约 6 倍。但是,我没有做任何优化它,尤其是在消除底层代码中更深层次的字符串(在某些情况下它转换为字符串以运行 .ToLower() 只是为了返回 char 数组)。

    欢迎投稿。下面讨论了从 2014 年开始代码在 Mono 中的工作方式(就在它从 Mono 中删除之前不久,用于 Microsoft 基于字符串的实现):

    System.Text.RegularExpressions.Regex 使用RxCompilerRxInterpreterFactory 的形式实例化IMachineFactory,这毫不奇怪使IMachines 成为RxInterpreters。让它们发射是您需要做的大部分事情,尽管如果您只是想了解它是如何为效率而构建的,那么值得注意的是,您正在寻找的大部分内容都在它的基类中,BaseMachine

    特别是在BaseMachine 中是基于StringBuilder 的东西。在方法LTRReplace 中,它首先用初始字符串实例化一个StringBuilder,从那里开始的一切都是纯粹基于StringBuilder 的。如果我们假设内部 Microsoft .Net 实现是相似的,那么 Regex 没有 StringBuilder 方法,这实际上很烦人。

    【讨论】:

    • 您好,您的 2. 替换仿真有一个错误:original.Substring(pos, match.Index) 应该是 original.Substring(pos, match.Index - pos),因为 Substring 的第二个参数是长度,而不是位置。对 Substring 的两个调用都有这个问题。除此之外,代码运行良好,但比 .net core 3.1 上的 string.replace 稍慢。
    【解决方案3】:

    我不确定这是否有助于您的方案,但我遇到了 Regex 的一些内存消耗上限,我需要在 StringBuilder 上使用简单的通配符替换扩展方法来克服它。如果您需要复杂的正则表达式匹配和/或反向引用,则不会这样做,但如果是简单的 * 或 ?通配符替换(使用文字“替换”文本)将为您完成工作,然后我的问题结尾处的解决方法至少应该给您一个提升:

    Has anyone implemented a Regex and/or Xml parser around StringBuilders or Streams?

    【讨论】:

      【解决方案4】:

      这是一种扩展方法,您可以使用它来完成您想要的。它接受Dictionary,其中键是您要查找的模式,值是您要替换它的值。您仍然创建传入字符串的副本,但您只需处理一次,而不是为多次调用 Regex.Replace 创建副本。

      public static StringBuilder BulkReplace(this StringBuilder source, IDictionary<string, string> replacementMap)
      {
          if (source.Length == 0 || replacementMap.Count == 0)
          {
              return source;
          }
          string replaced = Regex.Replace(source.ToString(), String.Join("|", replacementMap.Keys.Select(Regex.Escape).ToArray()), m => replacementMap[m.Value], RegexOptions.IgnoreCase);
          return source.Clear().Append(replaced);
      }
      

      【讨论】:

      • 将 Regex 与 StringBuilder 结合使用的目的不仅仅是要有一个方法来完成这项工作,而是要尽量减少内存浪费,特别是避免将大量中间字符串存储在内存中。
      • 这并不完美,因为您必须将 StringBuilder 转换为字符串,但此方法比简单地在字符串上一遍又一遍地调用 Regex.Replace 快约 4 倍。
      • 如果 replacementMap 包含模式,你会得到:“给定的键不在字典中”。这是意料之中的,因为 replacementMap[m.Value] 中的 m.Value 查找的键是与模式匹配的 actula 字符串,而不是模式本身。我错过了什么吗?模式是指正则表达式模式字符串,例如:“]+>”,而不是像“
        ”这样的精确字符串
      猜你喜欢
      • 2013-06-26
      • 1970-01-01
      • 2019-01-24
      • 2011-07-13
      • 2015-01-24
      • 1970-01-01
      • 1970-01-01
      • 2011-09-07
      相关资源
      最近更新 更多