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

Obi*_*obi 255 c# 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();
    }
Run Code Online (Sandbox Code Playgroud)

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

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

Guf*_*ffa 313

为什么你认为你的方法效率不高?它实际上是您可以做到的最有效的方法之一.

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

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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

原始功能:54.5毫秒.
我建议的改变:47.1 ms.
设置StringBuilder容量为43.3毫秒.
正则表达式:294.4 ms.

编辑2:我在上面的代码中添加了AZ和az之间的区别.(我重新进行了性能测试,并没有明显的区别.)

编辑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);
}
Run Code Online (Sandbox Code Playgroud)

  • @downvoter:为什么选择downvote?如果你不解释你认为错误的东西,它就无法改善答案. (10认同)
  • 我同意.我要做的唯一其他更改是将初始容量参数添加到StringBuilder构造函数,"= new StringBuilder(str.Length)". (4认同)
  • 根据我的测试,我的答案是使用`char []`缓冲区而不是`StringBuilder`,在这个缓冲区上略有优势.(虽然我的可读性较差,但小的性能优势可能不值得.) (2认同)
  • @SILENT:不,它没有,但你应该只做一次.如果在每次调用方法时分配一个大的数组(并且如果经常调用该方法),那么该方法到目前为止变得最慢,并且导致垃圾收集器的大量工作. (2认同)

Bli*_*ixt 183

好吧,除非你真的需要从你的功能中挤出性能,否则就去使用最容易维护和理解的东西.正则表达式如下所示:

为了获得额外的性能,您可以预先编译它,也可以告诉它在第一次调用时编译(后续调用会更快.)

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

  • @rmeador:没有它被编译它大约慢5倍,编译它比他的方法慢3倍.尽管如此仍然简单10倍:-D (9认同)
  • 它是一个非常简单的正则表达式(没有回溯或任何复杂的东西)所以它应该非常快. (6认同)
  • 正则表达式不是神奇的锤子,也不会比手动优化的代码更快. (6认同)
  • 对于那些记得Knuth关于优化的着名引言的人来说,这是从哪里开始的.然后,如果您发现需要额外千分之一毫秒的性能,请使用其他技术之一. (2认同)

Ste*_*dit 15

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

编辑

此外,为了提高速度,您需要将StringBuilder的容量初始化为输入字符串的长度.这将避免重新分配.这两种方法将为您提供速度和灵活性.

另一个编辑

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


Luk*_*keH 12

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个.我调整了这个方法`foreach(char c in input.Where(c => char.IsLetterOrDigit(c)|| allowedSpecialCharacters.Any(x => x == c)))buffer [idx ++] = c;` (2认同)

CMS*_*CMS 11

正则表达式如下所示:

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

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


Sha*_*ser 10

如果您使用的是动态字符列表,LINQ可能会提供更快更优雅的解决方案:

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

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

  • LukeH的Char阵列解决方案 - 427 ms
  • StringBuilder解决方案 - 429毫秒
  • LINQ(这个答案) - 98毫秒

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

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

  • Char阵列解决方案 - 7ms
  • StringBuilder解决方案 - 22ms
  • LINQ - 60毫秒

如果您计划编写更通用的解决方案,而不是对字符列表进行硬编码,那么可能值得查看LINQ或修改后的方法.LINQ绝对能为您提供简洁,高度可读的代码 - 甚至比Regex还要强大.

  • 这种方法看起来不错,但不起作用-Except()是set操作,因此最终只能看到字符串中每个唯一字符的首次出现。 (2认同)

lc.*_*lc. 5

我不相信你的算法不算高效.它是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] == '.')
Run Code Online (Sandbox Code Playgroud)

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

switch (str[i])
{
    case '0':
    case '1':
    .
    .
    .
    case '.':
        sb.Append(str[i]);
        break;
}
Run Code Online (Sandbox Code Playgroud)

  • O(n)符号有时会让我生气.人们会根据算法已经为O(n)的事实做出愚蠢的假设.如果我们改变了这个例程,用一个函数来替换str [i]调用,该函数通过与世界另一端的服务器构建一次性S​​SL连接来检索比较值...你真的会看到一个巨大的性能差异和算法是STILL O(n).每种算法的O(1)成本都很高,而且不相同! (7认同)

Gio*_* M. 5

您可以使用正则表达式如下:

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