2009-01-18 79 views
2

我有一個簡單的程序地帶XML非法的所有字符的字符串:LINQ性能影響

string SanitizeXml(string xml) 
{ 
    return string.Concat 
     (xml.ToCharArray().Where(c => IsLegalXmlChar(c)).ToArray()); 
} 

很高興和簡潔。但我擔心它的表現。同樣的事情可以很容易地通過一個簡單的for循環來完成:

string SanitizeXml(string xml) 
{ 
    var buffer = new StringBuilder(); 

    foreach(char c in xml) 
    { 
     if (IsLegalXmlChar(c)) 
     { 
      buffer.Append(c); 
     } 
    } 

    return buffer.ToString(); 
} 

什麼伸出我的是,在第二個例子中,XML轉換爲char [],並在()的IEnumerable的<char>回到char []。我似乎用LINQ做了很多 - 在數組和枚舉之間進行更改。

我應該關心這個嗎?總的來說,當我有一個明確的替代方案時,依靠LINQ擴展方法會帶來什麼樣的性能衝擊,這可能會更加冗長。

也許這是一個太寬泛的問題。

+0

爲什麼要刪除非法字符,而不是讓框架爲你逃脫? – 2009-01-18 19:30:13

+0

非法字符通常會通過文檔和數據轉換進入。例如,沒有理由在XML文檔中使用'\ b',事實上,您的主板會產生鈴聲,任何嘗試使用這些數據的.NET XML API都會拋出異常。 – core 2009-01-24 21:41:55

回答

6

那麼,你不需要第一個電話ToCharArray()開始 - 字符串實現IEnumerable<char>。不過,我同意在這種情況下,StringBuilder和循環可能更合適。

我不知道什麼string.Concat(char [])做副手,順便說一句 - 爲什麼你不只是使用字符串構造函數,它需要一個字符數組?換句話說,經過這些修改:

static string SanitizeXml(string xml) 
{ 
    return new string (xml.Where(c => IsLegalXmlChar(c)).ToArray()); 
} 

我還是更喜歡StringBuilder的解決方案,但可能對於通常的情況得到改善(其中有幾個非法字符)給予開始用適當的容量:

string SanitizeXml(string xml) 
{ 
    var buffer = new StringBuilder(xml.Length); 

    foreach(char c in xml) 
    { 
     if (IsLegalXmlChar(c)) 
     { 
       buffer.Append(c); 
     } 
    } 

    return buffer.ToString(); 
} 

一種替代我之前沒有想到的可能是對的StringBuilder擴展方法:

// Can't just call it Append as otherwise StringBuilder.Append(object) would 
// be used :(
public static StringBuilder AppendSequence(this StringBuilder builder, 
              IEnumerable<char> sequence) 
{ 
    foreach (char c in sequence) 
    { 
     builder.Append(c); 
    } 
    return builder; 
} 

然後,你可以使用它像這樣:

xml = new StringBuilder(xml.Length) 
      .AppendSequence(xml.Where(IsLegalXmlChar) 
      .ToString(); 

(你可以有其他重載AppendSequence採取的IEnumerable等,如果你想。)

編輯:另一種方法是避免調用追加經常使用,而不是the overload which appends a substring。然後,您可以再建立StringBuilder的,像一個擴展方法(沒有經過充分測試,我害怕 - 我還沒有嘗試過,甚至其編譯):

public static StringBuilder AppendWhere(this StringBuilder builder, 
             string text, 
             Func<char, bool> predicate) 
{ 
    int start = 0; 
    bool lastResult = false; 
    for (int i=0; i < text.Length; i++) 
    { 
     if (predicate(text[i])) 
     { 
      if (!lastResult) 
      { 
       start = i; 
       lastResult = true; 
      } 
     } 
     else 
     { 
      if (lastResult) 
      { 
       builder.Append(text, start, i-start); 
       lastResult = false; 
      } 
     } 
    } 
    if (lastResult) 
    { 
     builder.Append(text, start, text.Length-start); 
    } 
    return builder; 
} 

使用的例子:

xml = new StringBuilder(xml.Length).AppendWhere(xml, IsLegalXmlChar) 
            .ToString(); 

另一種方法是將其更改爲String上的擴展方法,懶惰地創建StringBuilder,並且如果以start = 0結束,只返回原始字符串。

0

就我個人而言,我不會在這種情況下使用LINQ /擴展方法; LINQ是一個強大的工具,但它不應該用於每個問題。

根據定義,使用Ienumerable<T>ToArray等的現有LINQ擴展方法將爲您的方案增加一些開銷。問題是:對你的情況有意義嗎?例如,如果你要做數據的網絡傳輸,這裏幾皮秒是沒有關係的。但是如果你正在緊密循環中執行xml,它可能會發生。

一個更好的解決將是直接與框架代碼編碼它...我去看看,如果我能找到這個最簡單的選項...

注:這裏的另一個微優化將預 - 使用現有字符串的長度初始化StringBuilder

+0

是否將XML數據加載到XText()中會自動對它進行消毒處理? – core 2009-01-18 09:57:32

+0

是的(例如:`string s = new XText(「x&y」)。ToString();`) - 可能**合理**有效。 – 2009-01-18 10:01:09

3

對於簡單的foreach循環,兩個版本基本相同。

想想爲什麼我們有IEnumerable類型在第一位,它用於foreach循環!如果你使用foreach循環,那麼你的字符串在後臺被轉換爲IEnumerable,所以邏輯本質上與LINQ版本中的相同。

除非您進行一些優化,即使用StringBuilder,否則性能不會太差。

這裏有一個分析代碼:http://pastebin.com/f125a9a46

信用@克里斯,@Marc_Garvell,@Jon_Skeet

這是我的機器上的結果:

Simple LINQ version      : 43270ms 
For-each-loop version w/ StringBuilder : 35875ms 
For-each-loop version w/ List   : 37595ms 
For-index loop w/ StringBuilder   : 37589ms 
Jon Skeet's AppendWhere version   : 28980ms 

這裏的啓用代碼優化的結果:

Simple LINQ version      : 27814ms 
For-each-loop version w/ StringBuilder : 23453ms 
For-each-loop version w/ List   : 21374ms 
For-index loop w/ StringBuilder   : 22308ms 
Jon Skeet's AppendWhere version   : 10884ms 

4.3秒差異在LINQ和foreach循環之間並不真正證明400,000,000個字符,當你也考慮到你已經使用了StringBuilder而LINQ有從char數組重新構建的開銷時。

-1

我會使用正則表達式來清理字符串。它看起來比正在討論的選項更清潔。有人可以用正則表達式來運行性能測試,並讓我知道如果我做錯了什麼?

謝謝!