我想从字符串中删除所有特殊字符。允许输入A-Z(大写或小写)、数字(0-9)、下划线(_)或点符号(.)。

我有以下,它是有效的,但我怀疑(我知道!)它不是很有效:

    public static string RemoveSpecialCharacters(string str)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.Length; i++)
        {
            if ((str[i] >= '0' && str[i] <= '9')
                || (str[i] >= 'A' && str[i] <= 'z'
                    || (str[i] == '.' || str[i] == '_')))
                {
                    sb.Append(str[i]);
                }
        }

        return sb.ToString();
    }

最有效的方法是什么?正则表达式是什么样子的,它与普通字符串操作相比如何?

要清洗的字符串相当短,长度通常在10到30个字符之间。


当前回答

如果你使用的是动态字符列表,LINQ可以提供一个更快更优雅的解决方案:

public static string RemoveSpecialCharacters(string value, char[] specialCharacters)
{
    return new String(value.Except(specialCharacters).ToArray());
}

我将这种方法与之前的两种“快速”方法(发行版编译)进行了比较:

字符数组解决方案由LukeH - 427毫秒 StringBuilder解决方案- 429毫秒 LINQ(这个答案)- 98毫秒

注意,算法略有修改-字符作为数组传入,而不是硬编码,这可能会有轻微的影响(即/其他解决方案将有一个内部for循环来检查字符数组)。

如果我使用LINQ where子句切换到硬编码的解决方案,结果是:

字符数组解决方案- 7ms StringBuilder解决方案- 22ms LINQ - 60毫秒

如果您计划编写一个更通用的解决方案,而不是硬编码字符列表,那么可能值得考虑LINQ或经过修改的方法。LINQ绝对能给你简洁、高可读性的代码——甚至比Regex更好。

其他回答

正则表达式的使用方法如下:

return Regex.Replace(strIn, @"[^\w\.@-]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0));

我同意这个代码示例。唯一不同的是,我把它变成字符串类型的扩展方法。所以你可以在非常简单的一行或代码中使用它:

string test = "abc@#$123";
test.RemoveSpecialCharacters();

感谢Guffa的实验。

public static class MethodExtensionHelper
    {
    public static string RemoveSpecialCharacters(this string str)
        {
            StringBuilder sb = new StringBuilder();
            foreach (char c in str)
            {
                if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
                {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }
}

下面的代码有以下输出(结论是,我们也可以节省一些内存资源分配数组更小的大小):

lookup = new bool[123];

for (var c = '0'; c <= '9'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'A'; c <= 'Z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'a'; c <= 'z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

48: 0  
49: 1  
50: 2  
51: 3  
52: 4  
53: 5  
54: 6  
55: 7  
56: 8  
57: 9  
65: A  
66: B  
67: C  
68: D  
69: E  
70: F  
71: G  
72: H  
73: I  
74: J  
75: K  
76: L  
77: M  
78: N  
79: O  
80: P  
81: Q  
82: R  
83: S  
84: T  
85: U  
86: V  
87: W  
88: X  
89: Y  
90: Z  
97: a  
98: b  
99: c  
100: d  
101: e  
102: f  
103: g  
104: h  
105: i  
106: j  
107: k  
108: l  
109: m  
110: n  
111: o  
112: p  
113: q  
114: r  
115: s  
116: t  
117: u  
118: v  
119: w  
120: x  
121: y  
122: z  

你也可以添加以下代码行来支持俄语区域设置(数组大小为1104):

for (var c = 'А'; c <= 'Я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'а'; c <= 'я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

简单的LINQ方法

string text = "123a22 ";
var newText = String.Join(string.Empty, text.Where(x => x != 'a'));

另一种试图通过减少分配来提高性能的方法,特别是在多次调用此函数的情况下。

它之所以有效,是因为可以保证结果不会比输入长,因此可以在不在内存中创建额外副本的情况下传递输入和输出。因此,您不能使用stackalloc来创建缓冲区数组,因为这需要从缓冲区中复制一个副本。

public static string RemoveSpecialCharacters(this string str)
{
    return RemoveSpecialCharacters(str.AsSpan()).ToString();
}

public static ReadOnlySpan<char> RemoveSpecialCharacters(this ReadOnlySpan<char> str)
{
    Span<char> buffer = new char[str.Length];
    int idx = 0;

    foreach (char c in str)
    {
        if (char.IsLetterOrDigit(c))
        {
            buffer[idx] = c;
            idx++;
        }
    }

    return buffer.Slice(0, idx);
}