为什么正则表达式和StringBuilder在删除空白处速度较慢?

问题描述:

我们正在将通信与外部API集成。到目前为止,由于命名不一致,文档较差以及不可靠的响应/错误消息,这一直是一件令人头痛的事情。为什么正则表达式和StringBuilder在删除空白处速度较慢?

我们正在处理的事情之一是我们发送给他们的某些请求对字符串的长度有限制。没有突破性的,但任何包含任何超过长度要求的字符串的请求都被拒绝并失败。

我们的解决方案是创建字符串的扩展方法,只是发生在最大长度和索引返回长度开始的子串0

我是一个初中dev的,这是我的第一份工作,所以我知道我的解决方案可能不是最优雅或高效的。无论哪种方式,我提出了这样一个观点,即在我们目前的扩展中,我们最终可能会删除相关信息,同时包括潜在的毫无价值的空白空间,因为我们没有修整或做任何事情来检查双空格等。我的领导告诉我可以*地做出扩展的过载可以让你选择去除空白。

我想出了3种解决方案,可以完全消除任何双重空间。我意识到Regex方法是唯一一个真正去除所有空白区域的方法,其中另外两个方法将两个空格紧接着排除。然而,这个网站将在美国使用,所以我不确定是否需要额外的正则表达式时间。

我在发布这个主要兴趣是我想知道是否有人可以解释为什么我的方法使用StringBuilder比其他两个效率低,它甚至比正则表达式慢,我预计它是三者中最快的。这里的任何见解都是值得赞赏的,同时也暗示了可能比我提出的任何更好的方法。

这里是我的三个分机:

public static string SafeSubstringSomehowTheQuickest(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 

     stringToShorten = stringToShorten.Trim(); 
     int stringOriginalLength = stringToShorten.Length; 
     int extraWhitespaceCount = 0; 
     for (int i = 0; i < stringOriginalLength - extraWhitespaceCount; i++) 
     { 
      int stringLengthBeforeReplace = stringToShorten.Length; 
      stringToShorten = stringToShorten.Replace(" ", " "); 
      if(stringLengthBeforeReplace < stringToShorten.Length) { extraWhitespaceCount += stringToShorten.Length - stringLengthBeforeReplace; } 
     } 

     return stringToShorten.Length > maxLength ? stringToShorten.Substring(0, maxLength) : stringToShorten; 
    } 

    public static string SafeSubstringWithRegex(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 
     stringToShorten = System.Text.RegularExpressions.Regex.Replace(stringToShorten, @"\s{2,}", " ").Trim(); 

     return stringToShorten.Length > maxLength ? stringToShorten.Substring(0, maxLength) : stringToShorten; 
    } 

    public static string SafeSubstringFromBuilder(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 

     StringBuilder bob = new StringBuilder(); 
     bool lastCharWasWhitespace = false; 

     foreach (char c in stringToShorten) 
     { 
      if (c == ' ' && !lastCharWasWhitespace) { bob.Append(c); } 
      lastCharWasWhitespace = c == ' '; 
      if (!lastCharWasWhitespace) { bob.Append(c); } 
     } 
     stringToShorten = bob.ToString().Trim(); 

     return stringToShorten.Length < maxLength ? stringToShorten : stringToShorten.Substring(0, maxLength); 
    } 

这是我简单的测试我使用的是比较花费的时间为每个分机运行:

static void Main(string[] args) 
    { 
     var stopwatch = new System.Diagnostics.Stopwatch(); 

     string test = 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      "; 

     int stringStartingLength = test.Length; 
     int stringMaxLength = 30; 

     stopwatch.Start(); 
     string somehowTheQuickestResult = test.SafeSubstringSomehowTheQuickest(stringMaxLength); 
     stopwatch.Stop(); 
     var somehowTheQuickestResultTicks = stopwatch.ElapsedTicks; 

     stopwatch.Start(); 
     string regexResult = test.SafeSubstringWithRegex(stringMaxLength); 
     stopwatch.Stop(); 
     var regexResultTicks = stopwatch.ElapsedTicks; 

     stopwatch.Start(); 
     string stringBuilderResult = test.SafeSubstringFromBuilder(stringMaxLength); 
     stopwatch.Stop(); 
     var stringBuilderResultTicks = stopwatch.ElapsedTicks; 
    } 

最后这些都是结果,每次运行时刻度会有所不同,但三种方法之间的差异相当一致:

所有三个方法返回相同的字符串的: “富酒吧foobar的˚FOO酒吧foobar的”

somehowTheQuickestResult(方法1):12840蜱

regexResult(方法2):14889蜱

stringBuilderResult(方法3):15798 ticks

+0

如果你使用了一个字符数组,并且将非空白字符移动过来,它可能会更快。对于较大的字符串比较,您可能会得到与当前方法截然不同的结果 – BugFinder

+0

作为一个附注,所有方法会返回一串很大的空格,即使它太长了。 – GSerg

+1

对于正则表达式,您可能希望通过使用必需的[flags](https://msdn.microsoft.com/zh-cn/library/h5845fdz(v = vs)创建明确的“静态只读”实例来排除编译时间0.110)的.aspx)。对于stringbuilder,您可能想要将'maxLength'作为['capacity'](https://msdn.microsoft.com/en-us/library/h1h0a5sy(v = vs.110).aspx)来传递。 – GSerg

你在做你的基准测试有点不对。

首先,你需要“热身”,让JIT做好工作。基本上,只需调用你的三种方法并放弃结果。

接下来,单次尝试并不具有代表性。尝试平均(或中位时间)超过100次或更多的迭代。

三,您的使用Stopwatch是错误的。 之后Start()恢复间隔测量。 Restart()是要走的路。有了它,我的测试显示的结果如下:

9569 
314 
58 

所以,StringBuilder方式实际上是最快的国家之一。

+0

很棒!对不起,这是我第一次使用秒表。打算重做这个,并在几千个周期内获得更好的平均值。 – WRP