确定字符串长度是否不为0的最有效方式是什么?

13

我正在尝试加速以下过程:

string s; //--> s is never null

if (s.Length != 0)
{
   <do something>
}

问题是,似乎.Length实际上计算的是字符串中的字符数,这比我需要的工作要多得多。有没有人有加速的想法?

或者,有没有办法确定s [0]是否存在,而不必检查字符串的其余部分?


1
你面临的性能问题很可能已经在其他地方得到了解决!你进行了分析并确定瓶颈确实在这里吗? - Aryabhatta
你是针对哪个版本的框架进行开发的呢? - Conrad Frix
请注意:对于其他可能回答的人,Eric在下面发布了一个澄清他的问题的答案(有点)。 - Dan Puzey
9个回答

24

编辑: 现在你提供了更多背景:

  • 尝试复现这个问题时,我并没有发现 string.Length 有瓶颈。唯一让它变得更快的方法是注释掉测试和 if 块体中的内容——这并不公平。只注释掉条件会减慢速度,也就是说无条件复制引用比检查条件要慢。

  • 如先前指出的那样,使用重载的 string.Split 方法可以为你去除空条目,这才是真正的优化器。

  • 你还可以进一步优化,避免每次都创建只含一个空格的新 char 数组。你总是会有效地传递相同的内容,因此为什么不利用它呢?

  • 空数组实际上是不可变的。你可以通过始终返回相同的结果来优化 null/empty 情况。

优化后的代码如下:

private static readonly char[] Delimiters = " ".ToCharArray();
private static readonly string[] EmptyArray = new string[0];

public static string[] SplitOnMultiSpaces(string text)
{
    if (string.IsNullOrEmpty(text))
    {
        return EmptyArray;
    }

    return text.Split(Delimiters, StringSplitOptions.RemoveEmptyEntries);
}
String.Length并不会准确地计算字符串中的字母数。该值是作为一个字段存储的,虽然我记得该字段的最高位被用于记住所有字符是否是ASCII(或者以前是这样),以启用其他优化。因此,属性访问可能需要执行按位掩码操作,但仍将是O(1),我希望JIT也会进行内联处理。(它是作为extern实现的,但希望这不会影响到在这种情况下的JIT - 我认为这是一个足够常见的操作,有可能得到特殊支持。)如果您已经知道字符串不为空,则您现有的测试可以简化为:
if (s.Length != 0)

在我看来,如果你追求原始性能,使用这种方式是最佳选择。在大多数情况下,我会这样编写:

if (s != "")

为了使其更清晰,我们关心的并不是字符串长度作为一个值,而是它是否为空字符串。这个测试比长度测试稍微慢一些,但我认为它更加清晰。像往常一样,在您拥有基准/分析数据表明这确实是一个瓶颈之前,我会选择最清晰的代码。我知道您的问题明确是要找到最有效的测试方法,但我想无论如何都要提一下。您有证据表明这是一个瓶颈吗?

编辑:只是为了更清楚地说明我建议不要使用string.IsNullOrEmpty的原因:调用该方法表明调用方明确地试图处理变量为null的情况,否则他们不会提到它。如果在代码的这个阶段,如果变量为null就算作一个bug的话,那么你不应该将其视为正常情况来处理。

在这种情况下,Length检查实际上比我提出的不等式测试方式在某些方面更好:它扮演了一个隐式的断言,即变量不为null。如果您有一个bug,并且它是null,则该测试将引发异常并及早检测到错误。如果您使用相等性测试,则会将null视为与空字符串不同,因此它将进入“if”语句的主体。如果您使用string.IsNullOrEmpty,则会将null视为与空相同,因此不会进入该块。


2
我认为这个最好的地方就在于最后一行。 - msarchet
这篇文章有些旧了,但是你能否提供一些关于“ANSI位”的信息呢?据我所知,长度是在字符串本身之前按原样存储的。unsafe void PeekLength(String s) { fixed (char* st = s) { try { int* l = (int*)(st - 2); Console.WriteLine("Length is " + *l); } catch(Exception e) { Console.WriteLine(e.Message); } } } - Sergey.quixoticaxis.Ivanov
@Sergey.quixoticaxis.Ivanov:假设您指的是ASCII而不是ANSI,我预计您的PeekLength方法有时会给出错误的答案,因为曾经有一个设置顶部位来表示“此字符串绝对全部为ASCII”的情况。不过我不知道现在是否仍然如此。 - Jon Skeet
@JonSkeet 确定,我是指 ASCII =) 在 4.7 上似乎不再是这种情况了。PeekLength 对于 "青空" 打印出 2。有没有可能在某个地方阅读相关信息以及其背后的原因? - Sergey.quixoticaxis.Ivanov
1
@Sergey.quixoticaxis.Ivanov:看起来这个测试用例不太好,因为它一开始就不是ASCII码...但我也记不清我从哪里听说过了。 - Jon Skeet
显示剩余3条评论

11

String.IsNullOrEmpty是检查null或空字符串的首选方法。

在内部,它将使用Length属性。但是应该避免实时计算字符串的长度。

如果您绝对确定该字符串永远不会为空,并且您对String.IsNullOrEmpty有一些强烈的反感,那么我能想到的最有效的代码是:

if(s.Length > 0)
{
    // Do Something
}

或者,可能更好的是:

if(s != "")
{
    // Do Something
}

很好,但如果已知字符串不为空,则这是过度设计。 - Tim Robinson
不仅过度了,而且它表明你认为这个变量有可能为空。 - Jon Skeet
然后将其与 String.Empty 进行比较。 - ankitjaininfo
1
在这种情况下,string.Length == 0 是确定字符串是否为空的最快方法。 - Matt Greer

5

访问Length属性不应该进行计数——.NET字符串在对象内部存储计数。

SSCLI/Rotor源代码包含了一个有趣的注释,它表明String.Length既有效率又神奇:

// Gets the length of this string
//
/// This is a EE implemented function so that the JIT can recognise is specially
/// and eliminate checks on character fetchs in a loop like:
/// for(int I = 0; I < str.Length; i++) str[i]
/// The actually code generated for this will be one instruction and will be inlined.
//
public extern int Length {
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    get;
}

3

因为你最初发布的内容与其他人之前发布的不匹配,根本无法编译。现在你已经修复了它以匹配其他人之前发布的内容,所以我撤回了它。 - Hut8
具体来说,如果(![String.IsNullOrEmpty] [1](yourstring)) - Hut8
我试图添加超链接,但后来意识到代码块无法实现。我应该先看预览。顺便说一句,感谢你的努力。 - ankitjaininfo

1
String.IsNullOrWhiteSpace(s);

如果 s 为 null 或空,或者 s 仅由空格字符组成,则为 true。


0
        for (int i = 0; i < 100; i++)
        {
            System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
            string s = "dsfasdfsdafasd";

            timer.Start();
            if (s.Length > 0)
            {
            }

            timer.Stop();
            System.Diagnostics.Debug.Write(String.Format("s.Length != 0 {0} ticks       ", timer.ElapsedTicks));

            timer.Reset();
            timer.Start();
            if (s == String.Empty)
            {
            }

            timer.Stop();
            System.Diagnostics.Debug.WriteLine(String.Format("s== String.Empty {0} ticks", timer.ElapsedTicks));
        }

使用秒表,s.length != 0 比 s == String.Empty 花费更少的时钟周期。
在我修复代码之后。

0

根据您在答案中描述的意图,为什么不尝试使用此Split内置选项:

s.Split(new[]{" "}, StringSplitOptions.RemoveEmptyEntries);

0

关于性能,一如既往的要进行基准测试。
使用C# 3.5或更早版本,您将想要测试yourString.LengthString.IsNullOrEmpty(yourString)之间的差异。

使用C# 4,执行上述两个操作并添加String.IsNullOrWhiteSpace(yourString)

当然,如果您知道您的字符串永远不会为空,您可以尝试访问s[0]并在不存在时处理异常。这通常不是一个好习惯,但它可能更接近您所需的内容(如果s应始终具有非空值)。


然而,根据问题中的要求,“IsNullOrWhitespace”会给出错误的答案。 - Jon Skeet

-1
只需使用 String.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries),它就可以为您完成所有操作。

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