更好的清理字符串的方法?

30

我正在使用这种方法来清理一个字符串:

public static string CleanString(string dirtyString)
{
    string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
    string result = dirtyString;

    foreach (char c in removeChars)
    {
        result = result.Replace(c.ToString(), string.Empty);
    }

    return result;
}

这种方法可以得到正确的结果。然而,这种方法存在性能问题。每次传递字符串时,每个字符都要进入循环。如果我有一个很长的字符串,那么返回对象将需要太长时间。

有没有更好的方法做同样的事情?也许可以使用LINQ或jQuery/JavaScript吗?

任何建议都将不胜感激。


1
你清理字符串的目的是什么? - Russ Cam
2
将所有字符放入正则表达式的字符类中,然后一次性替换所有字符。 - nhahtdh
这能用正则表达式完成吗? - Stuart.Sklinar
2
定义“更好”的含义。任何解决方案都需要循环遍历字符。你代码的缺点在于过多创建字符串对象,而不是每个字符的循环遍历。 - hatchet - done with SOverflow
7
您需要的是HttpUtility.HtmlEncode而不是字符串清理。 - L.B
显示剩余7条评论
9个回答

52

好的,考虑以下测试:

public class CleanString
{
    //by MSDN http://msdn.microsoft.com/en-us/library/844skk0h(v=vs.71).aspx
    public static string UseRegex(string strIn)
    {
        // Replace invalid characters with empty strings.
        return Regex.Replace(strIn, @"[^\w\.@-]", "");
    }

    // by Paolo Tedesco
    public static String UseStringBuilder(string strIn)
    {
        const string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
        // specify capacity of StringBuilder to avoid resizing
        StringBuilder sb = new StringBuilder(strIn.Length);
        foreach (char x in strIn.Where(c => !removeChars.Contains(c)))
        {
            sb.Append(x);
        }
        return sb.ToString();
    }

    // by Paolo Tedesco, but using a HashSet
    public static String UseStringBuilderWithHashSet(string strIn)
    {
        var hashSet = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
        // specify capacity of StringBuilder to avoid resizing
        StringBuilder sb = new StringBuilder(strIn.Length);
        foreach (char x in strIn.Where(c => !hashSet.Contains(c)))
        {
            sb.Append(x);
        }
        return sb.ToString();
    }

    // by SteveDog
    public static string UseStringBuilderWithHashSet2(string dirtyString)
    {
        HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
        StringBuilder result = new StringBuilder(dirtyString.Length);
        foreach (char c in dirtyString)
            if (removeChars.Contains(c))
                result.Append(c);
        return result.ToString();
    }

    // original by patel.milanb
    public static string UseReplace(string dirtyString)
    {
        string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
        string result = dirtyString;

        foreach (char c in removeChars)
        {
            result = result.Replace(c.ToString(), string.Empty);
        }

        return result;
    }

    // by L.B
    public static string UseWhere(string dirtyString)
    {
        return new String(dirtyString.Where(Char.IsLetterOrDigit).ToArray());
    }
}

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        var dirtyString = "sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf////fj()=/§(§&/(\"&sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf////fj()=/§(§&/(\"&sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf";
        var sw = new Stopwatch();

        var iterations = 50000;
        
        sw.Start();
        for (var i = 0; i < iterations; i++)
            CleanString.<SomeMethod>(dirtyString);
        sw.Stop();
        Debug.WriteLine("CleanString.<SomeMethod>: " + sw.ElapsedMilliseconds.ToString());
        sw.Reset();

        ....
        <repeat>
        ....       
    }
}

输出

CleanString.UseReplace: 791
CleanString.UseStringBuilder: 2805
CleanString.UseStringBuilderWithHashSet: 521
CleanString.UseStringBuilderWithHashSet2: 331
CleanString.UseRegex: 1700
CleanString.UseWhere: 233

总结

使用哪种方法可能并不重要。

当连续调用50000次时,最快的方法(UseWhere:233ms)和最慢的方法(UseStringBuilder:2805ms)之间的时间差为2572ms。如果你不频繁地运行该方法,这种差异实际上并不重要。

但是,如果性能很关键,请使用 UseWhere 方法(由L.B编写)。请注意,其行为略有不同。


在您的计算机上,return new String(dirtyString.Where(Char.IsLetterOrDigit).ToArray()) 会返回什么? - L.B
很快。50000次迭代:182毫秒(下一个是UseStringBuilderWithHashSet2,需要266毫秒)。 - sloth
9
仅供记录,对于 UseStringBuilderWithHashSet 和 UseStringBuilderWithHashSet2,测试将为 if (!removeChars.Contains(c)) - Guillaume Beauvois
L.B的UseWhere方法是否可以扩展以允许附加字符?如下: public static string UseWhereExtended(string dirtyString) { IEnumerable stringQuery = from ch in dirtyString where char.IsLetterOrDigit(ch) || ch == '.' || ch == ',' || ch == '\'' || ch == '\"' || ch == '?' || ch == '!' select ch; return new string(stringQuery.ToArray()); } - ATutorMe
1
我认为UseStringBuilderWithHashSet2中有一个错误,if(removeChars.Contains(c))应该改为if(!removeChars.Contains(c)) - Daxtron2

7
如果你只是追求速度和效率,我建议你这样做:
public static string CleanString(string dirtyString)
{
    HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
    StringBuilder result = new StringBuilder(dirtyString.Length);
    foreach (char c in dirtyString)
        if (!removeChars.Contains(c)) // prevent dirty chars
            result.Append(c);
    return result.ToString();
}

RegEx是一个优雅的解决方案,但它会增加额外的开销。通过指定字符串构建器的起始长度,它只需要分配一次内存(并在结尾处第二次分配用于ToString)。这将减少内存使用量并提高速度,特别是对于较长的字符串。
然而,正如L.B.所说,如果您正在使用它来正确编码即将输出到HTML的文本,则应该使用HttpUtility.HtmlEncode而不是自己编写代码。

output 应该改为 result。另外,可以省略 .ToCharArray(),因为字符串实现了 IEnumerable<char> 接口。 - sloth
1
你也可以使用一行代码 return new String(dirtyString.Where(c => !removeChars.Contains(c)).ToArray()); - L.B
你会如何在 removeChars 哈希表中添加空格? - Evaldas Raisutis
1
@Qweick 嗯,空格字符已经包含在内了,但如果您想要包含其他任何空白字符,只需将它们连接到字符串中即可(例如:"..."&vbTab)。 - Steven Doggart
@StevenDoggart 嗯,是的,谢谢 :) 不知为何我以为那里必须有一个符号 :)) - Evaldas Raisutis
显示剩余2条评论

4

使用正则表达式 [?&^$#@!()+-,:;<>’\'-_*] 用空字符串替换


2

我不确定在性能方面,使用Regex还是LINQ会有所改进。
创建新字符串时,使用StringBuilder而不是每次使用string.Replace可能会很有用:

using System.Linq;
using System.Text;

static class Program {
    static void Main(string[] args) {
        const string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
        string result = "x&y(z)";
        // specify capacity of StringBuilder to avoid resizing
        StringBuilder sb = new StringBuilder(result.Length);
        foreach (char x in result.Where(c => !removeChars.Contains(c))) {
            sb.Append(x);
        }
        result = sb.ToString();
    }
}

这确实有所帮助。使用StringBuilder类为我打开了一个新的思路。 - patel.milanb
"removeChars.Contains" 是 "O(n)"。使用 "HashSet" 会更好。 - L.B

2
这个更快! 使用:
string dirty=@"tfgtf$@$%gttg%$% 664%$";
string clean = dirty.Clean();


    public static string Clean(this String name)
    {
        var namearray = new Char[name.Length];

        var newIndex = 0;
        for (var index = 0; index < namearray.Length; index++)
        {
            var letter = (Int32)name[index];

            if (!((letter > 96 && letter < 123) || (letter > 64 && letter < 91) || (letter > 47 && letter < 58)))
                continue;

            namearray[newIndex] = (Char)letter;
            ++newIndex;
        }

        return new String(namearray).TrimEnd();
    }


1
也许先解释“为什么”,然后再解释“什么”会有所帮助。你的性能变慢的原因是因为C#为每个替换复制和替换字符串。从我的经验来看,在.NET中使用正则表达式并不总是更好 - 尽管在大多数情况下(我认为包括这种情况)它可能会很好地工作。
如果我真的需要性能,我通常不会把它留给运气,而是直接告诉编译器我想要什么:也就是说,创建一个具有上限字符数的字符串,并将所有需要的字符复制到其中。还可以用switch / case或数组替换哈希集,这样你可能会得到一个跳转表或数组查找 - 这甚至更快。
“务实”的最佳但快速的解决方案是:
char[] data = new char[dirtyString.Length];
int ptr = 0;
HashSet<char> hs = new HashSet<char>() { /* all your excluded chars go here */ };
foreach (char c in dirtyString)
    if (!hs.Contains(c))
        data[ptr++] = c;
return new string(data, 0, ptr);

顺便提一下:当您想要处理高代理Unicode字符时,这个解决方案是不正确的 - 但可以很容易地进行调整以包括这些字符。

-Stefan。


1
我在当前项目中使用它,它能正常工作。它会获取一个句子,删除所有非字母数字字符,然后返回首字母大写的单词和其他所有单词小写的句子。也许我应该称之为SentenceNormalizer。命名很难 :)
    internal static string StringSanitizer(string whateverString)
{
    whateverString = whateverString.Trim().ToLower();
    Regex cleaner = new Regex("(?:[^a-zA-Z0-9 ])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
    var listOfWords = (cleaner.Replace(whateverString, string.Empty).Split(' ', StringSplitOptions.RemoveEmptyEntries)).ToList();
    string cleanString = string.Empty;
    foreach (string word in listOfWords)
    {
        cleanString += $"{word.First().ToString().ToUpper() + word.Substring(1)} ";
    }
    return cleanString;
}

0

我无法花时间酸测试这个问题,但是这行代码实际上没有像预期的一样正确清理斜杠。

HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");

我必须逐个添加反斜杠并转义反斜杠

HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’'-_*");
removeChars.Add('/');
removeChars.Add('\\');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接