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

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

其他回答

public static string RemoveAllSpecialCharacters(this string text) {
  if (string.IsNullOrEmpty(text))
    return text;

  string result = Regex.Replace(text, "[:!@#$%^&*()}{|\":?><\\[\\]\\;'/.,~]", " ");
  return result;
}

你为什么认为你的方法没有效率?这实际上是最有效的方法之一。

当然,你应该将字符读入局部变量或使用枚举器来减少数组访问的次数:

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

使这种方法高效的一个原因是它具有良好的可伸缩性。执行时间将与字符串的长度相关。如果你把它用在一根大弦上,就不会有令人讨厌的意外。

编辑: 我做了一个快速的性能测试,用24个字符串运行每个函数一百万次。结果如下:

原功能:54.5毫秒。 我建议的修改:47.1毫秒。 采矿设置StringBuilder容量:43.3 ms。 正则表达式:294.4毫秒。

编辑2: 我在上面的代码中添加了A-Z和A-Z之间的区别。(我重新进行了性能测试,没有明显的差异。)

编辑3: 我测试了lookup+char[]解决方案,它运行大约13毫秒。

当然,这样做的代价是初始化庞大的查找表并将其保存在内存中。好吧,虽然没有那么多数据,但对于这样一个微不足道的函数来说,已经够多了……

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

我觉得你的算法很有效。它是O(n),并且只查看每个字符一次。除非你在检查它们之前神奇地知道它们的值,否则你不会比这更好。

但是,我将把StringBuilder的容量初始化为字符串的初始大小。我猜您认为的性能问题来自内存重新分配。

旁注:检查A-z并不安全。你要包括[,\,],^,_和'…

旁注2:为了获得额外的效率,将比较按顺序排列,以减少比较的数量。(在最坏的情况下,你说的是8个比较,所以不要想太多。)这随你的预期输入而变化,但一个例子可以是:

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

旁注3:如果出于某种原因,你真的需要它更快,那么switch语句可能更快。编译器应该为你创建一个跳转表,结果只有一个比较:

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

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

我测试了,这并不比公认的答案快。 如果你需要一组可配置的字符,我会把它留在这里,这将是一个很好的解决方案。

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