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

14

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

有很多方法可以实现这一点:

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方法似乎通常更快。

感谢所有人的回答。我将选择Andy的答案作为“正确答案”,因为他付出了努力并且Jon已经有数十亿的声誉了。

8个回答

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
}

你能给我的答案(IsNullOrEmpty)进行基准测试吗?这样我们就可以在一个基准测试环境和一个系统上拥有所有的方法了。 - pyrocumulus
也要尝试其他测试用例,包括 "" 和 " x "。否则你只测试了一条路径。 - Jon Skeet
为了让你的基准测试更公平,难道不应该在测试失败时也测量所需时间吗?在平均情况下最快的方法可能取决于你预计测试成功的频率。 - Simon Nickerson
哦,还有“x”(即无需修剪)。以及长字符串 :) - Jon Skeet
也可以添加只包含空格的长字符串。 - leppie

15

不要使用trim函数——它可能会创建一个你实际上不需要的新字符串。相反,检查该字符串中除了(根据你想要的定义)空白字符之外的任何字符。例如:

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,您还需考虑方法的操作。

可能需要尝试的进一步微观优化:

  • Is foreach faster or slower than using a for loop like the one below? Note that with the for loop you can remove the "if (text.Length==0)" test at the start.

    for (int i = 0; i < text.Length; i++)
    {
        char c = text[i];
        // ...
    
  • Same as above, but hoisting the Length call. Note that this isn't good for normal arrays, but might be useful for strings. I haven't tested it.

    int length = text.Length;
    for (int i = 0; i < length; i++)
    {
        char c = text[i];
    
  • In the body of the loop, is there any difference (in speed) between what we've got and:

    if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
    {
        return false;
    }
    
  • Would a switch/case be faster?

    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、使用text.Lengthfor、使用text.Length一次并反转迭代顺序的for以及具有提升长度的for之间的差异。
基本上,for循环速度略快,但提升长度检查会使其比foreach更慢。反转for循环方向比foreach稍微慢一些。我强烈怀疑JIT在这里做了一些有趣的事情,例如删除重复的边界检查等。
代码:(有关编写此代码的框架,请参见我的基准测试博客文章
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

根据字符串的长度,从前面和后面检查字符串可能会更快,或者我错了?想想一个字符串,在开头有50个空格,但在结尾只有一个非空格字符。 - Felix K.
这就是我想的,对于长字符串来说,交替使用开头和结尾。在最坏的情况下,所需时间是从开头开始时所需时间的两倍。在最好的情况下,它可以节省您迭代整个字符串所需的时间。 - Felix K.
@FelixK:啊,之前我误解了你。明白了。 - Jon Skeet
@FelixK.:但是最终,在长度为N的字符串中,最坏情况下你永远不会比O(N)更好,对吧? - Jon Skeet
@FelixK.:就我个人而言,说实话,我发现在末尾只有空格更为常见。 - Jon Skeet
显示剩余27条评论

4

我真的不知道哪个更快;尽管我的直觉告诉我是第一个选项。但这里有另一种方法:

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

1
除非为空,否则它将抛出 NullPointerException。 - Chad Grant
2
没错,但是其他方法都无法通过“myString = null”的情况。所以就这个问题而言,这不是什么大问题。 - pyrocumulus
我认为这很令人困惑,因为myString必须一开始就不为空。 - Danko Durbić
可能会有些困惑,我可以理解。但他正在寻求最快的方法。显然,这个建议是其中较快的一个 :) - pyrocumulus

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()) 始终是最快的。以上结果是进行1000万次比较的典型结果。


@womp:string.IsNullOrEmpty除了进行null检查外,还会检查字符串长度;在这种情况下,我更喜欢直接检查它-选项1。 - Dan C.

3

1
这个方法确实是在.NET 4.0中添加的(2010年),请参阅String.IsNullOrWhiteSpace方法 - Jeppe Stig Nielsen

3

检查字符串长度是否为零是测试空字符串最有效的方法,因此我会说是第一种:

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

唯一进一步优化的方法可能是通过使用编译好的正则表达式避免修剪(编辑:实际上比使用Trim().Length慢得多)。
编辑:使用Length的建议来自FxCop指南。我也进行了测试:比与空字符串比较快2-3倍。但是,这两种方法都非常快(我们在谈论纳秒级别的速度)-所以使用哪种方法几乎没有关系。修剪是一个更大的瓶颈,比最后的比较慢数百倍。

你有任何论据来支持那个说法吗? - pyrocumulus
你能解释一下吗?别误会,我相信你,但我很感兴趣。 - Damovisa
字符串被存储在带有指示字符串长度的头部中,因此字符串长度检查只需返回该值。 - Richard Ev

1

由于我刚开始,无法发表评论,所以就是这样。

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

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
}

我也打算发表这个评论,很高兴看到你已经发表了。 - Felan
当myString为空时,您打算如何修剪? - nawfal
我不明白你的评论,nawfal。 - Oakcool
首先,Trim不是字符串的属性,而是方法。其次,如果字符串为空,为什么要修剪它呢? - glen3b

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接