【问题标题】:Why is Regex and StringBuilder slower at removing white space?为什么 Regex 和 StringBuilder 删除空格的速度较慢?
【发布时间】:2016-12-02 22:20:29
【问题描述】:

我们正在处理与外部 API 集成通信。到目前为止,由于命名不一致、文档不完善以及响应/错误消息不可靠,这一直是一个令人头疼的问题。

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

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

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

我提出了 3 个解决方案,可以完全删除任何双空格。我知道 Regex 方法是唯一真正删除所有空格的方法,而其他两个方法是背靠背删除任何出现的两个空格。但是这个网站将只在美国使用,所以我不确定 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;
    }

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

所有三个都返回相同的字符串:“foo bar foobar foo bar foobar”

somehowTheQuickestResult(方法 1):12840 滴答声

regexResult(方法 2):14889 个滴答声

stringBuilderResult(方法 3):15798 个滴答声

【问题讨论】:

  • 如果您使用字符数组并将非空白字符移到上面可能会更快。对于更大的字符串比较,您当前的方法可能会得到非常不同的结果跨度>
  • 附带说明,所有方法都将返回一个包含大量空格的字符串,即使它太长。
  • 对于正则表达式,您可能希望通过创建一个显式的 static readonly 实例来排除编译时间,并且需要 flags。对于字符串生成器,您可能希望将maxLength 作为capacity 传递。
  • @GSerg 不确定您的第一条评论是什么意思?如果字符串比 maxLength 短,那么我只返回它,因为在这种情况下,我不关心删除空格。删除空格的目的是在字符串超过 maxLength 时允许尽可能多的相关信息与请求一起传递。因此,我认为每种方法的第一行和最后一行都解决了您指出的问题。至于您的第二条评论,感谢您提供的信息,我将研究这两个选项并检查结果!
  • @WRP 如果您传递一个包含一千个空格且仅包含其他内容的字符串,它将原封不动地返回。 stringToShorten?.Length &lt; maxLength 将是 false,string.IsNullOrWhiteSpace(stringToShorten) 将是 true,false || true 是 true。

标签: c# regex replace stringbuilder removing-whitespace


【解决方案1】:

您的基准测试有点错误。

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

其次,单试不代表。尝试超过 100 次或更多次迭代的平均(或中值时间)。

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

9569
314
58

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

【讨论】:

  • 伟大的收获!抱歉,这是我第一次使用秒表。将努力重做,并在数千个周期内获得更好的平均值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-02
  • 2012-03-04
  • 1970-01-01
  • 2012-10-16
  • 2011-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多