在C#中确定未修剪字符串是否为空的最有效方法是什么?

时间:2009-05-01 06:53:16

标签: c# performance optimization coding-style string

我有一个字符串,周围可能有空白字符,我想查看它是否基本上是空的。

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

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的帖子是非常值得的。

  3. 似乎有一些非常有效的方法,大多数效率取决于我需要处理的字符串的内容。

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

    感谢大家的投入。我将选择安迪的答案作为“正确”的答案,仅仅是因为他投入的努力值得声望得到提升,乔恩已经拥有了十亿美元的声誉。

8 个答案:

答案 0 :(得分:19)

编辑:新测试:

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
}

答案 1 :(得分:15)

(编辑:请参阅帖子的底部,了解该方法的不同微优化的基准)

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

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

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

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

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

<强>基准 我已经对我的原始建议进行了一些基准测试,并且我的投入似乎在我投入的所有内容中获胜,这让我感到惊讶,因为其他答案的结果。不过,我还使用foreach fortext.Length使用for一次,然后反转迭代顺序,text.Length和{{{ 1}}悬挂长度。

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

代码:(请参阅my benchmarking blog entry了解反写的框架)

foreach

结果:

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

答案 2 :(得分:4)

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

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

答案 3 :(得分:4)

myString.Trim()。长度== 0 获取:421毫秒

myString.Trim()==''花了: 468 ms

if(myString.Trim()。Equals(“”))Took: 515 ms

if(myString.Trim()== String.Empty)取得:484毫秒

if(myString.Trim()。Equals(String.Empty)) Took:500 ms

if(string.IsNullOrEmpty(myString.Trim())) Took:437 ms

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

答案 4 :(得分:3)

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

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

进一步优化这种方法的唯一方法可能是避免使用编译的正则表达式进行修剪(编辑:这实际上比使用Trim()慢得多。长度)。

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

答案 5 :(得分:3)

String.IsNullOrWhitespace in .NET 4 Beta 2也在这个空间中播放,不需要自定义编写

答案 6 :(得分:1)

因为我刚刚开始,所以我不能发表评论。

if (String.IsNullOrEmpty(myString.Trim()))
如果myString为null,则

Trim()调用将失败,因为您无法调用null(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
}

答案 7 :(得分:0)

public static bool IsNullOrEmpty(this String str, bool checkTrimmed)
{
  var b = String.IsNullOrEmpty(str);
  return checkTrimmed ? b && str.Trim().Length == 0 : b;
}