【问题标题】:What's the most efficient way to determine whether an untrimmed string is empty in C#?在 C# 中确定未修剪的字符串是否为空的最有效方法是什么?
【发布时间】:2010-10-23 01:34:22
【问题描述】:

我有一个字符串,它周围可能有空格字符,我想检查它是否本质上是空的。

有很多方法可以做到这一点:

1  if (myString.Trim().Length == 0)
2  if (myString.Trim() == "")
3  if (myString.Trim().Equals(""))
4  if (myString.Trim() == String.Empty)
5  if (myString.Trim().Equals(String.Empty))

我知道这通常是过早优化的明显案例,但我很好奇,并且有可能这样做足以对性能产生影响。

那么哪种方法最有效?

有没有更好的方法我没有想到?


编辑:此问题访问者的注意事项:

  1. 已经对这个问题进行了一些非常详细的调查 - 特别是来自 Andy 和 Jon Skeet。

  2. 如果您在搜索某些内容时偶然发现了这个问题,那么至少值得您完整阅读 Andy 和 Jon 的帖子。

似乎有几个非常有效的方法,最有效的取决于我需要处理的字符串的内容。

如果我不能预测字符串(在我的情况下我不能),Jon 的 IsEmptyOrWhiteSpace 方法通常似乎更快。

感谢大家的意见。我将选择安迪的答案作为“正确”答案,仅仅是因为他付出的努力值得声誉提升,而乔恩已经拥有 1100 亿的声誉。

【问题讨论】:

    标签: c# performance optimization coding-style string


    【解决方案1】:

    编辑:新测试:

    Test orders:
    x. Test name
    Ticks: xxxxx //Empty String
    Ticks: xxxxx //two space
    Ticks: xxxxx //single letter
    Ticks: xxxxx //single letter with space
    Ticks: xxxxx //long string
    Ticks: xxxxx //long string  with space
    
    1. if (myString.Trim().Length == 0)
    ticks: 4121800
    ticks: 7523992
    ticks: 17655496
    ticks: 29312608
    ticks: 17302880
    ticks: 38160224
    
    2.  if (myString.Trim() == "")
    ticks: 4862312
    ticks: 8436560
    ticks: 21833776
    ticks: 32822200
    ticks: 21655224
    ticks: 42358016
    
    
    3.  if (myString.Trim().Equals(""))
    ticks: 5358744
    ticks: 9336728
    ticks: 18807512
    ticks: 30340392
    ticks: 18598608
    ticks: 39978008
    
    
    4.  if (myString.Trim() == String.Empty)
    ticks: 4848368
    ticks: 8306312
    ticks: 21552736
    ticks: 32081168
    ticks: 21486048
    ticks: 41667608
    
    
    5.  if (myString.Trim().Equals(String.Empty))
    ticks: 5372720
    ticks: 9263696
    ticks: 18677728
    ticks: 29634320
    ticks: 18551904
    ticks: 40183768
    
    
    6.  if (IsEmptyOrWhitespace(myString))  //See John Skeet's Post for algorithm
    ticks: 6597776
    ticks: 9988304
    ticks: 7855664
    ticks: 7826296
    ticks: 7885200
    ticks: 7872776
    
    7. is (string.IsNullOrEmpty(myString.Trim())  //Cloud's suggestion
    ticks: 4302232
    ticks: 10200344
    ticks: 18425416
    ticks: 29490544
    ticks: 17800136
    ticks: 38161368
    

    以及使用的代码:

    public void Main()
    {
    
        string res = string.Empty;
    
        for (int j = 0; j <= 5; j++) {
    
            string myString = "";
    
            switch (j) {
    
                case 0:
                    myString = "";
                    break;
                case 1:
                    myString = "  ";
                    break;
                case 2:
                    myString = "x";
                    break;
                case 3:
                    myString = "x ";
                    break;
                case 4:
                    myString = "this is a long string for testing triming empty things.";
                    break;
                case 5:
                    myString = "this is a long string for testing triming empty things. ";
    
                    break;
            }
    
            bool result = false;
            Stopwatch sw = new Stopwatch();
    
            sw.Start();
            for (int i = 0; i <= 100000; i++) {
    
    
                result = myString.Trim().Length == 0;
            }
            sw.Stop();
    
    
            res += "ticks: " + sw.ElapsedTicks + Environment.NewLine;
        }
    
    
        Console.ReadKey();  //break point here to get the results
    }
    

    【讨论】:

    • 您能否对我的答案 (IsNullOrEmpty) 进行基准测试?然后我们在一个工作台和一个系统上拥有所有方法。
    • 也可以尝试其他测试用例——包括“”和“x”。否则你只测试一个路径。
    • 为了让你的基准测试公平,如果测试是假的,你难道不应该测量所用的时间吗?一般情况下最快的方法可能取决于您期望测试成功的频率。
    • 哦,还有“x”(即不需要修剪)。还有长字符串:)
    • 添加一个只有空格的长字符串。
    【解决方案2】:

    (编辑:有关该方法的不同微优化的基准,请参见帖子底部)

    不要修剪它 - 这可能会创建一个您实际上不需要的新字符串。相反,请在字符串中查找任何 不是 空格的字符(对于您想要的任何定义)。例如:

    public static bool IsEmptyOrWhitespace(string text)
    {
        // Avoid creating iterator for trivial case
        if (text.Length == 0)
        {
            return true;
        }
        foreach (char c in text)
        {
            // Could use Char.IsWhiteSpace(c) instead
            if (c==' ' || c=='\t' || c=='\r' || c=='\n')
            {
                continue;
            }
            return false;
        }
        return true;
    }
    

    如果textnull,您还可以考虑您希望该方法做什么。

    可能进行进一步的微优化实验:

    • foreach 比使用下面的 for 循环快还是慢?请注意,使用for 循环,您可以在开始时删除“if (text.Length==0)”测试。

      for (int i = 0; i < text.Length; i++)
      {
          char c = text[i];
          // ...
      
    • 与上面相同,但提升了Length 调用。请注意,这 不适用于普通数组,但 可能 对字符串有用。我没有测试过。

      int length = text.Length;
      for (int i = 0; i < length; i++)
      {
          char c = text[i];
      
    • 在循环体中,我们得到的和:

      if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
      {
          return false;
      }
      
    • 开关/机箱会更快吗?

      switch (c)
      {
          case ' ': case '\r': case '\n': case '\t':
              return false;               
      }
      

    修剪行为更新

    我一直在研究Trim 如何能像这样高效。似乎Trim 只会在需要时创建一个新字符串。如果它可以返回this"",它将:

    using System;
    
    class Test
    {
        static void Main()
        {
            CheckTrim(string.Copy(""));
            CheckTrim("  ");
            CheckTrim(" x ");
            CheckTrim("xx");
        }
    
        static void CheckTrim(string text)
        {
            string trimmed = text.Trim();
            Console.WriteLine ("Text: '{0}'", text);
            Console.WriteLine ("Trimmed ref == text? {0}",
                              object.ReferenceEquals(text, trimmed));
            Console.WriteLine ("Trimmed ref == \"\"? {0}",
                              object.ReferenceEquals("", trimmed));
            Console.WriteLine();
        }
    }
    

    这意味着这个问题中的任何基准测试都应该使用混合数据,这一点非常重要:

    • 空字符串
    • 空格
    • 文本周围有空格
    • 没有空格的文本

    当然,这四者之间的“现实世界”平衡是无法预测的……

    基准测试 我已经对原始建议与我的建议进行了一些基准测试,而且我的似乎在我投入的所有内容中都获胜,考虑到其他答案的结果,这让我感到惊讶。但是,我还对 foreachfor 使用 text.Lengthfor 使用 text.Length 一次然后反转迭代顺序以及 for 与提升长度之间的差异进行了基准测试。

    基本上for 循环稍微快一点,但提升长度检查使其比foreach 慢。反转for 循环方向也比foreach 慢得多。我强烈怀疑 JIT 在这里做了一些有趣的事情,比如删除重复的边界检查等。

    代码:(请参阅 my benchmarking blog entry 了解此框架所针对的编写)

    using System;
    using BenchmarkHelper;
    
    public class TrimStrings
    {
        static void Main()
        {
            Test("");
            Test(" ");
            Test(" x ");
            Test("x");
            Test(new string('x', 1000));
            Test(" " + new string('x', 1000) + " ");
            Test(new string(' ', 1000));
        }
    
        static void Test(string text)
        {
            bool expectedResult = text.Trim().Length == 0;
            string title = string.Format("Length={0}, result={1}", text.Length, 
                                         expectedResult);
    
            var results = TestSuite.Create(title, text, expectedResult)
    /*            .Add(x => x.Trim().Length == 0, "Trim().Length == 0")
                .Add(x => x.Trim() == "", "Trim() == \"\"")
                .Add(x => x.Trim().Equals(""), "Trim().Equals(\"\")")
                .Add(x => x.Trim() == string.Empty, "Trim() == string.Empty")
                .Add(x => x.Trim().Equals(string.Empty), "Trim().Equals(string.Empty)")
    */
                .Add(OriginalIsEmptyOrWhitespace)
                .Add(IsEmptyOrWhitespaceForLoop)
                .Add(IsEmptyOrWhitespaceForLoopReversed)
                .Add(IsEmptyOrWhitespaceForLoopHoistedLength)
                .RunTests()                          
                .ScaleByBest(ScalingMode.VaryDuration);
    
            results.Display(ResultColumns.NameAndDuration | ResultColumns.Score,
                            results.FindBest());
        }
    
        public static bool OriginalIsEmptyOrWhitespace(string text)
        {
            if (text.Length == 0)
            {
                return true;
            }
            foreach (char c in text)
            {
                if (c==' ' || c=='\t' || c=='\r' || c=='\n')
                {
                    continue;
                }
                return false;
            }
            return true;
        }
    
        public static bool IsEmptyOrWhitespaceForLoop(string text)
        {
            for (int i=0; i < text.Length; i++)
            {
                char c = text[i];
                if (c==' ' || c=='\t' || c=='\r' || c=='\n')
                {
                    continue;
                }
                return false;
            }
            return true;
        }
    
        public static bool IsEmptyOrWhitespaceForLoopReversed(string text)
        {
            for (int i=text.Length-1; i >= 0; i--)
            {
                char c = text[i];
                if (c==' ' || c=='\t' || c=='\r' || c=='\n')
                {
                    continue;
                }
                return false;
            }
            return true;
        }
    
        public static bool IsEmptyOrWhitespaceForLoopHoistedLength(string text)
        {
            int length = text.Length;
            for (int i=0; i < length; i++)
            {
                char c = text[i];
                if (c==' ' || c=='\t' || c=='\r' || c=='\n')
                {
                    continue;
                }
                return false;
            }
            return true;
        }
    }
    

    结果:

    ============ Length=0, result=True ============
    OriginalIsEmptyOrWhitespace             30.012 1.00
    IsEmptyOrWhitespaceForLoop              30.802 1.03
    IsEmptyOrWhitespaceForLoopReversed      32.944 1.10
    IsEmptyOrWhitespaceForLoopHoistedLength 35.113 1.17
    
    ============ Length=1, result=True ============
    OriginalIsEmptyOrWhitespace             31.150 1.04
    IsEmptyOrWhitespaceForLoop              30.051 1.00
    IsEmptyOrWhitespaceForLoopReversed      31.602 1.05
    IsEmptyOrWhitespaceForLoopHoistedLength 33.383 1.11
    
    ============ Length=3, result=False ============
    OriginalIsEmptyOrWhitespace             30.221 1.00
    IsEmptyOrWhitespaceForLoop              30.131 1.00
    IsEmptyOrWhitespaceForLoopReversed      34.502 1.15
    IsEmptyOrWhitespaceForLoopHoistedLength 35.690 1.18
    
    ============ Length=1, result=False ============
    OriginalIsEmptyOrWhitespace             31.626 1.05
    IsEmptyOrWhitespaceForLoop              30.005 1.00
    IsEmptyOrWhitespaceForLoopReversed      32.383 1.08
    IsEmptyOrWhitespaceForLoopHoistedLength 33.666 1.12
    
    ============ Length=1000, result=False ============
    OriginalIsEmptyOrWhitespace             30.177 1.00
    IsEmptyOrWhitespaceForLoop              33.207 1.10
    IsEmptyOrWhitespaceForLoopReversed      30.867 1.02
    IsEmptyOrWhitespaceForLoopHoistedLength 31.837 1.06
    
    ============ Length=1002, result=False ============
    OriginalIsEmptyOrWhitespace             30.217 1.01
    IsEmptyOrWhitespaceForLoop              30.026 1.00
    IsEmptyOrWhitespaceForLoopReversed      34.162 1.14
    IsEmptyOrWhitespaceForLoopHoistedLength 34.860 1.16
    
    ============ Length=1000, result=True ============
    OriginalIsEmptyOrWhitespace             30.303 1.01
    IsEmptyOrWhitespaceForLoop              30.018 1.00
    IsEmptyOrWhitespaceForLoopReversed      35.475 1.18
    IsEmptyOrWhitespaceForLoopHoistedLength 40.927 1.36
    

    【讨论】:

    • foreach(char c in text) 会实际进行内联搜索还是会创建一个新的字符数组?
    • 它将创建一个与字符串关联的适当 IEnumerator。它不会复制字符串。
    • 我刚刚用一百万次迭代测试了这个方法——它比使用 Trim() 稍微慢一些。不过,我想你可能会通过避免 GC 节省一点。
    • P.S.它还可能取决于(未修剪的)字符串有多长。
    • @Jon 谢谢你 - 很高兴知道。我一直在盲目使用 .ToCharArray()
    【解决方案3】:

    我真的不知道哪个更快;虽然我的直觉说第一。但这里有另一种方法:

    if (String.IsNullOrEmpty(myString.Trim()))
    

    【讨论】:

    • 除非它是 null 否则会抛出 NullPointerException
    • 是的,但是没有其他方法通过“myString = null”的情况。所以关于这个问题,没什么大不了的。
    • 我认为这很混乱,因为 myString 不能以 null 开头。
    • 这可能会令人困惑,我同意这一点。但他要求的是最快的方法。显然,这个建议是更快的建议之一:)
    【解决方案4】:

    myString.Trim().Length == 0 耗时:421 毫秒

    myString.Trim() == '' 耗时: 468 毫秒

    if (myString.Trim().Equals("")) 耗时: 515 毫秒

    if (myString.Trim() == String.Empty) 耗时:484 毫秒

    if (myString.Trim().Equals(String.Empty)) 耗时:500 毫秒

    if (string.IsNullOrEmpty(myString.Trim())) 耗时:437 毫秒

    在我的测试中,它看起来像 myString.Trim().Length == 0,令人惊讶的是,string.IsNullOrEmpty(myString.Trim()) 始终是最快的。以上结果是进行 10,000,000 次比较的典型结果。

    【讨论】:

    • @womp: string.IsNullOrEmpty 也检查字符串长度(除了做空检查);在这种情况下,我更喜欢直接检查它 - 选项 1。
    【解决方案5】:

    检查字符串的长度是否为零是测试空字符串的最有效方法,所以我会说数字 1:

    if (myString.Trim().Length == 0)
    

    进一步优化这一点的唯一方法可能是通过使用已编译的正则表达式来避免修剪(编辑:这实际上比使用 Trim().Length 慢得多)。

    编辑:使用 Length 的建议来自 FxCop 指南。我也刚刚对其进行了测试:它比空字符串快 2-3 倍。然而,这两种方法仍然非常快(我们说的是纳秒) - 所以你使用哪一种并不重要。修剪更像是一个瓶颈,它比最后的实际比较慢数百倍。

    【讨论】:

    • 你有什么论据来支持这个说法吗?
    • 你能解释一下为什么吗?不要误会我的意思,我相信你,但我会感兴趣的。
    • 字符串存储时带有指示字符串长度的标头,因此 string.Length 检查仅返回该值。
    【解决方案6】:

    String.IsNullOrWhitespace in .NET 4 Beta 2也在这个空间里玩,不需要自定义写

    【讨论】:

    【解决方案7】:

    因为我刚开始我不能评论所以在这里。

    if (String.IsNullOrEmpty(myString.Trim()))
    

    如果 myString 为空,Trim() 调用将失败,因为您无法调用空对象 (NullReferenceException) 中的方法。

    所以正确的语法应该是这样的:

    if (!String.IsNullOrEmpty(myString))
    {
        string trimmedString = myString.Trim();
        //do the rest of you code
    }
    else
    {
        //string is null or empty, don't bother processing it
    }
    

    【讨论】:

    • 我也打算发表这个评论,很高兴看到你这样做。
    • 当 myString 为空时你将如何修剪?
    • 我不明白你的评论 nawfal。
    • 首先,Trim 不是字符串的属性,而是一种方法。其次,如果字符串 null或空,为什么要修剪它?
    【解决方案8】:
    public static bool IsNullOrEmpty(this String str, bool checkTrimmed)
    {
      var b = String.IsNullOrEmpty(str);
      return checkTrimmed ? b && str.Trim().Length == 0 : b;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-07-19
      • 1970-01-01
      • 1970-01-01
      • 2010-10-19
      • 2012-04-02
      • 1970-01-01
      • 1970-01-01
      • 2013-09-12
      • 2020-12-01
      相关资源
      最近更新 更多