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

1 c# regex stringbuilder replace 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);
    }
Run Code Online (Sandbox Code Playgroud)

这是我用来比较每个扩展程序运行所需时间的快速测试:

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

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

这三个都返回相同的字符串:"foo bar foobar f oo bar foobar"

不知何故TheQuickestResult(方法1):12840 滴答

regexResult(方法2):14889滴答

stringBuilderResult(方法3):15798滴答

Ant*_*lev 5

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

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

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

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

9569
314
58
Run Code Online (Sandbox Code Playgroud)

因此,StringBuilder办法实际上是最快的国家之一.