我想从字符串中删除所有特殊字符。允许输入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个字符之间。


当前回答

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

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);
}

其他回答

我想知道基于regx的替代品(可能是编译的)是否更快。必须测试,有人发现这是~5倍慢。

除此之外,您应该使用预期的长度初始化StringBuilder,这样在中间字符串增长时就不必复制它。

一个好的数字是原始字符串的长度,或者稍低一些(取决于函数输入的性质)。

最后,您可以使用一个查找表(范围0..127)来确定一个字符是否被接受。

正则表达式如下所示:

public string RemoveSpecialChars(string input)
{
    return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);
}

但如果性能非常重要,我建议你在选择“正则表达式路径”之前做一些基准测试……

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

它之所以有效,是因为可以保证结果不会比输入长,因此可以在不在内存中创建额外副本的情况下传递输入和输出。因此,您不能使用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);
}
public static string RemoveSpecialCharacters(string str){
    return str.replaceAll("[^A-Za-z0-9_\\\\.]", "");
}

这里有很多建议的解决方案,有些比其他的更有效,但可能不是很好读。这里有一个可能不是最有效的,但在大多数情况下肯定是可用的,并且非常简洁易读,利用Linq:

string stringToclean = "This is a test.  Do not try this at home; you might get hurt. Don't believe it?";

var validPunctuation = new HashSet<char>(". -");

var cleanedVersion = new String(stringToclean.Where(x => (x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());

var cleanedLowercaseVersion = new String(stringToclean.ToLower().Where(x => (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());