2009-01-18 81 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

我会使用正则表达式来清理字符串。它看起来比正在讨论的选项更清洁。有人可以用正则表达式来运行性能测试,并让我知道如果我做错了什么?

谢谢!