【问题标题】:String.Replace() vs. StringBuilder.Replace()String.Replace() 与 StringBuilder.Replace()
【发布时间】:2011-09-25 08:31:41
【问题描述】:

我有一个字符串,我需要用字典中的值替换标记。它必须尽可能高效。用 string.replace 循环只会消耗内存(字符串是不可变的,记住)。 StringBuilder.Replace() 会更好吗,因为它是为字符串操作而设计的?

我希望避免 RegEx 的费用,但如果这样会更有效,那就这样吧。

注意:我不关心代码的复杂性,只关心它的运行速度和它消耗的内存。

平均统计数据:长度为 255-1024 个字符,字典中有 15-30 个键。

【问题讨论】:

标签: c# .net .net-4.0 replace


【解决方案1】:

使用 RedGate Profiler 使用以下代码

class Program
    {
        static string data = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
        static Dictionary<string, string> values;

        static void Main(string[] args)
        {
            Console.WriteLine("Data length: " + data.Length);
            values = new Dictionary<string, string>()
            {
                { "ab", "aa" },
                { "jk", "jj" },
                { "lm", "ll" },
                { "yz", "zz" },
                { "ef", "ff" },
                { "st", "uu" },
                { "op", "pp" },
                { "x", "y" }
            };

            StringReplace(data);
            StringBuilderReplace1(data);
            StringBuilderReplace2(new StringBuilder(data, data.Length * 2));

            Console.ReadKey();
        }

        private static void StringReplace(string data)
        {
            foreach(string k in values.Keys)
            {
                data = data.Replace(k, values[k]);
            }
        }

        private static void StringBuilderReplace1(string data)
        {
            StringBuilder sb = new StringBuilder(data, data.Length * 2);
            foreach (string k in values.Keys)
            {
                sb.Replace(k, values[k]);
            }
        }

        private static void StringBuilderReplace2(StringBuilder data)
        {
            foreach (string k in values.Keys)
            {
                data.Replace(k, values[k]);
            }
        }
    }
  • String.Replace = 5.843ms
  • StringBuilder.Replace #1 = 4.059ms
  • Stringbuilder.Replace #2 = 0.461ms

字符串长度 = 1456

stringbuilder #1 在方法中创建 stringbuilder 而 #2 没有,因此性能差异最终很可能是相同的,因为您只是将工作移出方法。如果您从 stringbuilder 而不是 string 开始,那么 #2 可能是要走的路。

就内存而言,使用 RedGateMemory 分析器,在您进行许多替换操作之前,无需担心 stringbuilder 将在总体上获胜。

【讨论】:

  • 如果编译器要优化这个可以,它会改变 StringBuilderReplace1(data); 的行吗?到 StringBuilderReplace2(new StringBuilder(data, data.Length * 2));?只是好奇。我理解其中的区别,只是好奇你是否知道。
  • 我不明白为什么 SB 方法 2 这么快 - JIT 应该优化 SB #1 和 SB #2 以便它们在运行时是相同的。
  • @Dai 请记住,这是在 2011 年。从那时起情况可能已经发生了变化。
  • @Dai -(延迟响应)-如答案中所述,分析器仅测量实际功能的经过时间。由于 Replace#2 中的 stringbuilder 声明在函数之外,因此构造时间不包括在经过的时间中。
  • 所以您是说 StringBuilderReplace1 的 89% 的替换时间只是初始化 StringBuilder 实例?大约只有 11%(4.059 的 0.461)时间花在了密钥替换上,就像在 StringBuilderReplace2 中隔离的那样?如果是这样...我会分配一个 StringBuilder 缓冲区并限制一定程度的并行性,以便使用现有实例进行批处理。现在,问题是...... StringBuilder.ToString 增加了什么时间?因为,公平地说,你的目标输出毕竟是一个字符串,只有第一种方法真正产生了一个字符串。
【解决方案2】:

这可能会有所帮助: https://docs.microsoft.com/en-us/archive/blogs/debuggingtoolbox/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance

简短的回答似乎是 String.Replace 更快,尽管它可能会对您的内存占用/垃圾收集开销产生更大的影响。

【讨论】:

【解决方案3】:

是的,StringBuilder 会给你带来速度和内存的增益(基本上是因为它不会在你每次使用它进行操作时创建一个字符串的实例 - StringBuilder 总是使用同一个对象) .这是MSDN link 的一些详细信息。

【讨论】:

  • 但是创建字符串生成器的开销值得吗?
  • @Dustin:可能有 15-30 次替换。
  • @Henk - 有 15-30 次搜索,不一定有那么多替换。每个字符串的预期平均标记数很重要。
  • @Joe:将进行 15-30 次扫描(可能正则表达式除外)。
  • @Henk,是的,但是 15-30 次扫描的性能对于 String 和 StringBuilder 将是相似的。任何证明创建字符串构建器开销的性能差异都必须来自替换,而不是扫描。
【解决方案4】:

stringbuilder.replace 会比 String.Replace 更好吗

是的,好多了。如果您可以估计新字符串的上限(看起来可以),那么它可能会足够快。

当你像这样创建它时:

  var sb = new StringBuilder(inputString, pessimisticEstimate);

那么 StringBuilder 将不必重新分配其缓冲区。

【讨论】:

    【解决方案5】:

    将数据从 String 转换为 StringBuilder 并返回需要一些时间。如果只执行一次替换操作,那么这个时间可能无法通过 StringBuilder 中固有的效率改进来弥补。另一方面,如果将字符串转换为 StringBuilder,然后对其执行多次替换操作,最后再将其转换回来,则 StringBuilder 方法往往更快。

    【讨论】:

      【解决方案6】:

      比起对整个字符串运行 15-30 次替换操作,使用类似 trie 的数据结构来保存字典可能更有效。然后你可以遍历你的输入字符串一次来完成你所有的搜索/替换。

      【讨论】:

        【解决方案7】:

        这在很大程度上取决于给定字符串中平均存在多少个标记。

        在 StringBuilder 和 String 之间搜索键的性能可能相似,但如果您必须替换单个字符串中的多个标记,则 StringBuilder 会胜出。

        如果您平均每个字符串只期望一个或两个标记,并且您的字典很小,我会选择 String.Replace。

        如果有很多标记,您可能需要定义自定义语法来识别标记 - 例如用适合文字大括号的转义规则括在大括号中。然后,您可以实现一个解析算法,该算法遍历字符串的字符一次,识别并替换它找到的每个标记。或者使用正则表达式。

        【讨论】:

        • +1 表示正则表达式 - 请注意,如果这样做,实际替换可以使用 MatchEvaluator 实际进行字典查找。
        【解决方案8】:

        我的两分钱在这里,我只是写了几行代码来测试每个方法的执行情况,正如预期的那样,结果是“它取决于”。

        对于较长的字符串,Regex 似乎表现更好,对于较短的字符串,String.Replace 是。我可以看到StringBuilder.Replace 的使用不是很有用,如果使用不当,从GC 的角度来看可能是致命的(我尝试分享一个StringBuilder 的实例)。

        查看我的StringReplaceTests GitHub repo

        【讨论】:

          【解决方案9】:

          @DustinDavis 的答案的问题是它递归地对同一个字符串进行操作。除非您打算进行来回类型的操作,否则在这种测试中,您确实应该为每个操作案例设置单独的对象。

          我决定创建自己的测试,因为我在网上发现了一些相互矛盾的答案,我想完全确定。我正在开发的程序处理大量文本(在某些情况下,文件有数万行)。

          所以这里有一个快速的方法,您可以复制和粘贴并自己查看哪个更快。您可能必须创建自己的文本文件进行测试,但您可以轻松地从任何地方复制和粘贴文本,并为自己制作一个足够大的文件:

          using System;
          using System.Diagnostics;
          using System.IO;
          using System.Text;
          using System.Windows;
          
          void StringReplace_vs_StringBuilderReplace( string file, string word1, string word2 )
          {
              using( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) )
              using( StreamReader streamReader = new StreamReader( fileStream, Encoding.UTF8 ) )
              {
                  string text = streamReader.ReadToEnd(),
                         @string = text;
                  StringBuilder @StringBuilder = new StringBuilder( text );
                  int iterations = 10000;
          
                  Stopwatch watch1 = new Stopwatch.StartNew();
                  for( int i = 0; i < iterations; i++ )
                      if( i % 2 == 0 ) @string = @string.Replace( word1, word2 );
                      else @string = @string.Replace( word2, word1 );
                  watch1.Stop();
                  double stringMilliseconds = watch1.ElapsedMilliseconds;
          
                  Stopwatch watch2 = new Stopwatch.StartNew();
                  for( int i = 0; i < iterations; i++ )
                      if( i % 2 == 0 ) @StringBuilder = @StringBuilder .Replace( word1, word2 );
                      else @StringBuilder = @StringBuilder .Replace( word2, word1 );
                  watch2.Stop();
                  double StringBuilderMilliseconds = watch1.ElapsedMilliseconds;
          
                  MessageBox.Show( string.Format( "string.Replace: {0}\nStringBuilder.Replace: {1}",
                                                  stringMilliseconds, StringBuilderMilliseconds ) );
              }
          }
          

          我得到了 string.Replace() 每次换出 8-10 个字母的单词时,速度会快 20%。如果您想要自己的经验证据,请自行尝试。

          【讨论】:

            猜你喜欢
            • 2013-10-03
            • 1970-01-01
            • 2013-05-08
            • 2010-12-09
            • 2010-09-22
            • 2012-02-28
            • 2011-08-05
            • 1970-01-01
            相关资源
            最近更新 更多