2011-06-29 44 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個字母的單詞。如果你想要自己的經驗證據,可以自己嘗試。