为什么Regex和StringBuilder在删除空格时速度较慢?

时间:2016-07-28 13:26:16

标签: c# regex replace stringbuilder removing-whitespace

我们正在处理与外部API集成通信的过程。到目前为止,由于命名不一致,文档较差以及不可靠的响应/错误消息,这一点令人头疼。

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

我们的解决方案是为字符串创建一个扩展方法,该方法只接受最大长度并返回从索引0开始的该长度的子字符串。

我是一名初级开发者,这是我的第一份工作,所以我知道我的解决方案可能不是最优雅或最有效的。无论哪种方式,我都提出了这样一个观点,即我们目前的扩展可能最终删除相关信息,同时包括可能毫无价值的空白空间,因为我们没有修剪或做任何事情来检查双重空间等。我的带领告诉我随意做一个扩展的重载,允许您选择删除空格。

我已经提出了3种解决方案,可以完全消除任何双重空间。我知道Regex方法是唯一一个真正删除所有空白区域的方法,而其他两个方法正在删除任意两个空格的背靠背。但是,这个网站将仅在美国使用,所以我不确定是否有必要使用正则表达式的额外时间。

我对发布这个内容的主要兴趣是我想知道是否有人可以解释为什么我使用StringBuilder的方法与其他两个相比效率低,它比Regex慢,我预计它会是三个中最快的。这里的任何见解都是值得赞赏的,也暗示了可能比我提出的更好的方式。

以下是我的三个扩展程序:

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

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

所有三个都返回相同的字符串:&#34; foo bar foobar f oo bar foobar&#34;

某种程度上最快的结果(方法1): 12840滴答

regexResult(方法2): 14889滴答

stringBuilderResult(方法3): 15798 ticks

1 个答案:

答案 0 :(得分:5)

你的基准测试有点不对劲。

首先,你需要“热身”并让JIT完成它的工作。基本上,只需调用您的三种方法并丢弃结果。

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

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

9569
314
58

所以,StringBuilder方式 实际上是最快的。