2011-06-29 85 views
65

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

我一直希望避免RegEx的开支,但如果这样做会更有效率,那就这样吧。

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

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

+0

标记和值的模式(长度)是什么? –

+0

短。标记5-15,值5-25 –

+0

可能的重复http://stackoverflow.com/questions/287842/is-stringbuilder-replace-more-efficient-than-string-replace –

回答

60

使用展鹏探查使用以下代码

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没有这样的表现差异最终会被相同的最有可能的,因为你只是移动的工作了的方法。如果你从一个字符串开始而不是一个字符串,那么#2可能是改变方向。

至于内存,采用RedGateMemory探查,没有什么可担心的,直到你到许多替换其中的StringBuilder是要全面赢得业务。

+0

如果编译器对此进行优化,它是否会改变StringBuilderReplace1(data)的行;到StringBuilderReplace2(新的StringBuilder(data,data.Length * 2));?只是好奇。我了解其中的差异,只要你知道就好奇。 – pqsk

+0

我不明白为什么SB方法2要快得多 - JIT应该优化SB#1和SB#2,以便它们在运行时保持一致。 – Dai

+0

@戴记住这是在2011年。事情可能从那时起就发生了变化。 –

9

这可能会有所帮助:

http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance.aspx

简短的回答似乎是与string.replace速度更快,但它可能对你的内存占用量/垃圾收集开销较大的冲击。

+0

有趣。根据他们的测试,string.replace更好。我在想,由于字符串字符串的小尺寸。替换会更好考虑任何开销创建一个字符串生成器 –

5

会更好stringbuilder.replace是[比与string.replace]

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

当你创建喜欢:

var sb = new StringBuilder(inputString, pessimisticEstimate); 

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

6

是的,StringBuilder会给你增加速度和内存(主要是因为它不会创建一个字符串的实例,每次你将使用它进行操作 - StringBuilder始终与同一个对象一起运行)。这里有一个MSDN link的一些细节。

+0

,但它是值得创建字符串生成器的开销? –

+1

@Dustin:可能有15-30个替代品。 –

+0

+1,因为如果您有很多操作,它是值得的。 –

1

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

1

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

1

这将取决于平均有多少标记出现在给定的字符串中。

寻找一个关键的性能很可能是StringBuilder的和字符串之间的相似,但如果你有一个字符串替换许多标记的StringBuilder将获胜。

如果你只希望每串一个或两个标记的平均值,和你的字典是小,我只想去了与string.replace。

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

+0

正则表达式的+1 - 请注意,如果这样做了,实际替换可以使用'MatchEvaluator'来实际执行字典查找。 – Random832

1

我在这里的两分钱,我只写了几个代码来测试每个方法如何执行行和,一如预期,结果是“看情况”。

对于更长的字符串Regex似乎表现更好,更短的字符串,String.Replace它。我可以看到StringBuilder.Replace的用法并不是非常有用,如果错误地使用它,它可能会在GC的角度上致命(我试图共享StringBuilder的一个实例)。

检查我的StringReplaceTests GitHub repo

1

与@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()是由大约快20%每次换出8-10个字母的单词。如果你想要自己的经验证据,可以自己尝试。