从string中删除特殊字符的最有效的方法

我想从string中删除所有特殊字符。 允许的字符是AZ(大写或小写),数字(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(); } 

什么是最有效的方法来做到这一点? 正则expression式是什么样的,它和普通的string操作有什么区别呢?

待清洁的琴弦相当短,通常在10到30个字符之间。

你为什么认为你的方法效率不高? 这实际上是你能做到的最有效的方法之一。

您当然应该将字符读入局部variables或使用枚举器来减less数组访问次数:

 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 == '.' || c == '_') { sb.Append(c); } } return sb.ToString(); } 

有一种方法是有效的,就是它可以很好地扩展。 执行时间将相对于string的长度。 如果你在一个大的string上使用它,没有令人讨厌的惊喜。

编辑:
我做了一个快速的性能testing,用24个字符的string运行每个函数一百万次。 这是结果:

原始function:54.5毫秒。
我build议的更改:47.1毫秒。
设置StringBuilder容量的矿:43.3毫秒。
正则expression式:294.4毫秒。

编辑2:我在上面的代码中添加了AZ和az之间的区别。 (我重新进行了性能testing,没有什么明显的差别。)

编辑3:
我testing了lookup + char []解决scheme,它运行大约13毫秒。

支付的价格当然是巨大的查找表的初始化,并将其保存在内存中。 那么,这不是那么多的数据,但这是一个微不足道的function…

 private static bool[] _lookup; static Program() { _lookup = new bool[65536]; for (char c = '0'; c <= '9'; c++) _lookup[c] = true; for (char c = 'A'; c <= 'Z'; c++) _lookup[c] = true; for (char c = 'a'; c <= 'z'; c++) _lookup[c] = true; _lookup['.'] = true; _lookup['_'] = true; } public static string RemoveSpecialCharacters(string str) { char[] buffer = new char[str.Length]; int index = 0; foreach (char c in str) { if (_lookup[c]) { buffer[index] = c; index++; } } return new string(buffer, 0, index); } 

那么,除非你真的需要从你的function中挤出性能,否则就去做最容易保持和理解的东西。 正则expression式看起来像这样:

为了获得额外的性能,您可以预先编译它,或者直接告诉它在第一次调用时编译(随后的调用会更快)。

 public static string RemoveSpecialCharacters(string str) { return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled); } 

我build议创build一个简单的查找表,您可以在静态构造函数中进行初始化,以将任何字符组合设置为有效。 这可以让你做一个快速,单一的检查。

编辑

另外,为了提高速度,你需要初始化你的StringBuilder的容量到inputstring的长度。 这将避免重新分配。 这两种方法一起给你速度和灵活性。

另一个编辑

我认为编译器可能会优化它,但作为风格和效率的问题,我推荐foreach而不是for。

正则expression式将如下所示:

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

但是,如果性能非常重要,我build议你select“正则expression式path”之前做一些基准testing…

 public static string RemoveSpecialCharacters(string str) { char[] buffer = new char[str.Length]; int idx = 0; foreach (char c in str) { if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '.') || (c == '_')) { buffer[idx] = c; idx++; } } return new string(buffer, 0, idx); } 

如果您使用dynamic的字符列表,LINQ可能会提供更快更优雅的解决scheme:

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

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

  • Chararrays解决schemeLukeH – 427 ms
  • StringBuilder解决scheme – 429毫秒
  • LINQ(这个答案) – 98毫秒

请注意,该algorithm稍微修改 – 字符作为数组传递,而不是硬编码,这可能会影响一些事情(即其他解决scheme将有一个内部foor循环来检查字符数组)。

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

  • 字符数组解决scheme – 7ms
  • StringBuilder解决scheme – 22ms
  • LINQ – 60毫秒

如果你打算编写一个更通用的解决scheme,而不是硬编码字符列表,可能值得看看LINQ或修改的方法。 LINQ绝对可以给你简明,高度可读的代码 – 甚至比正则expression式更好。

我不相信你的algorithm是有效的。 这是O(n),只看每个字符一次。 除非你在检查之前神奇地知道价值,否则你不会得到任何更好的结果。

然而,我会将你的StringBuilder的容量初始化为string的初始大小。 我猜你觉得性能问题来自内存重新分配。

附注:检查Az是不安全的。 你包括[\]^_和`…

附注2:为了提高效率,请按照最小化比较次数进行比较。 (最糟糕的是,你正在讨论8个比较,所以不要太难。)这会随着你预期的input而改变,但是一个例子可能是:

 if (str[i] >= '0' && str[i] <= 'z' && (str[i] >= 'a' || str[i] <= '9' || (str[i] >= 'A' && str[i] <= 'Z') || str[i] == '_') || str[i] == '.') 

附注3:如果由于某种原因,你真的需要这个速度很快,一个开关语句可能会更快。 编译器应该为你创build一个跳转表,结果只有一个比较:

 switch (str[i]) { case '0': case '1': . . . case '.': sb.Append(str[i]); break; } 

我会使用正则expression式search“特殊字符”的stringreplace,用空stringreplace所有find的字符。

对我来说似乎很好。 我所做的唯一的改进就是用string的长度初始化StringBuilder

 StringBuilder sb = new StringBuilder(str.Length); 
 StringBuilder sb = new StringBuilder(); for (int i = 0; i < fName.Length; i++) { if (char.IsLetterOrDigit(fName[i])) { sb.Append(fName[i]); } } 

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

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

我必须做类似的工作,但在我的情况下,我不得不过滤所有不是一个字母,数字或空白(但你可以很容易地修改它的需要)。 过滤是在JavaScript的客户端完成的,但出于安全原因,我也在做过滤服务器端。 由于我可以期望大部分的string是干净的,我想避免复制string,除非我真的需要。 这让我到下面的实现,这应该更好的干净和脏的string。

 public static string EnsureOnlyLetterDigitOrWhiteSpace(string input) { StringBuilder cleanedInput = null; for (var i = 0; i < input.Length; ++i) { var currentChar = input[i]; var charIsValid = char.IsLetterOrDigit(currentChar) || char.IsWhiteSpace(currentChar); if (charIsValid) { if(cleanedInput != null) cleanedInput.Append(currentChar); } else { if (cleanedInput != null) continue; cleanedInput = new StringBuilder(); if (i > 0) cleanedInput.Append(input.Substring(0, i)); } } return cleanedInput == null ? input : cleanedInput.ToString(); } 

这似乎没有效率。 您可能可以改进它,但是您可能会损害可读性/可维护性。

问候

对于S&G的Linq-ified方式:

 var original = "(*^%foo)(@)&^@#><>?:\":';=-+_"; var valid = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.', '_' }; var result = string.Join("", (from x in original.ToCharArray() where valid.Contains(x) select x.ToString()) .ToArray()); 

但是,我不认为这将是最有效的方式。

 public string RemoveSpecial(string evalstr) { StringBuilder finalstr = new StringBuilder(); foreach(char c in evalstr){ int charassci = Convert.ToInt16(c); if (!(charassci >= 33 && charassci <= 47))// special char ??? finalstr.append(c); } return finalstr.ToString(); } 

使用:

 s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end()); bool my_predicate(char c) { return !(isalpha(c) || c=='_' || c==' '); // depending on you definition of special characters } 

你会得到一个干净的string。

erase()将剥离所有特殊字符,并与my_predicate()函数高度可定制。

HashSet是O(1)
不知道它是否比现有的比较更快

 private static HashSet<char> ValidChars = new HashSet<char>() { 'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '_' }; public static string RemoveSpecialCharacters(string str) { StringBuilder sb = new StringBuilder(str.Length / 2); foreach (char c in str) { if (ValidChars.Contains(c)) sb.Append(c); } return sb.ToString(); } 

我testing了这个,并不比公认的答案快。
我会留下来,如果你需要一个可configuration的字符集这将是一个很好的解决scheme。

我不知道是否一个正则expression式replace(可能编译)是更快。 将不得不testing,有人发现这是慢了5倍。

除此之外,您应该初始化StringBuilder预期的长度,以便中间string不必在其增长时复制。

一个好的数字是原始string的长度,或稍微低一些(取决于函数input的性质)。

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

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

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

我不确定这是最有效的方法,但它适用于我

  Public Function RemoverTildes(stIn As String) As String Dim stFormD As String = stIn.Normalize(NormalizationForm.FormD) Dim sb As New StringBuilder() For ich As Integer = 0 To stFormD.Length - 1 Dim uc As UnicodeCategory = CharUnicodeInfo.GetUnicodeCategory(stFormD(ich)) If uc <> UnicodeCategory.NonSpacingMark Then sb.Append(stFormD(ich)) End If Next Return (sb.ToString().Normalize(NormalizationForm.FormC)) End Function 
 public static string RemoveSpecialCharacters(string str){ return str.replaceAll("[^A-Za-z0-9_\\\\.]", ""); } 

如果您担心速度,请使用指针来编辑现有的string。 您可以固定string并获取指针,然后在每个字符上运行for循环,用replace字符覆盖每个无效字符。 这将是非常有效的,不需要分配任何新的string内存。 您还需要使用不安全的选项来编译模块,并将“unsafe”修饰符添加到方法头以便使用指针。

 static void Main(string[] args) { string str = "string!$%with^&*invalid!!characters"; Console.WriteLine( str ); //print original string FixMyString( str, ' ' ); Console.WriteLine( str ); //print string again to verify that it has been modified Console.ReadLine(); //pause to leave command prompt open } public static unsafe void FixMyString( string str, char replacement_char ) { fixed (char* p_str = str) { char* c = p_str; //temp pointer, since p_str is read-only for (int i = 0; i < str.Length; i++, c++) //loop through each character in string, advancing the character pointer as well if (!IsValidChar(*c)) //check whether the current character is invalid (*c) = replacement_char; //overwrite character in existing string with replacement character } } public static bool IsValidChar( char c ) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '.' || c == '_'); //return char.IsLetterOrDigit( c ) || c == '.' || c == '_'; //this may work as well }