C# Contains比StartsWith快?

C# Contains比StartsWith快?,c#,.net,performance,string,C#,.net,Performance,String,昨天来了一位顾问,不知怎的,他提到了弦的话题。他提到,他已经注意到,对于小于一定长度的字符串,Contains实际上比StartsWith更快。我必须用自己的两只眼睛看,所以我写了一个小应用程序,果然,包含的更快 这怎么可能 DateTime start = DateTime.MinValue; DateTime end = DateTime.MinValue; string str = "Hello there"; start = DateTime.Now; for (int i = 0;

昨天来了一位顾问,不知怎的,他提到了弦的话题。他提到,他已经注意到,对于小于一定长度的字符串,
Contains
实际上比
StartsWith
更快。我必须用自己的两只眼睛看,所以我写了一个小应用程序,果然,
包含的
更快

这怎么可能

DateTime start = DateTime.MinValue;
DateTime end = DateTime.MinValue;
string str = "Hello there";

start = DateTime.Now;
for (int i = 0; i < 10000000; i++)
{
    str.Contains("H");
}
end = DateTime.Now;
Console.WriteLine("{0}ms using Contains", end.Subtract(start).Milliseconds);

start = DateTime.Now;
for (int i = 0; i < 10000000; i++)
{
    str.StartsWith("H");
}
end = DateTime.Now;
Console.WriteLine("{0}ms using StartsWith", end.Subtract(start).Milliseconds);

我也试过用更长的弦

尝试使用
秒表
来测量速度,而不是
日期时间
检查

我认为关键在于以下重要部分的加粗:

包含

此方法执行顺序 (区分大小写和 不区分区域性的比较

StartsWith

此方法执行一个 (区分大小写和区分区域性) 使用当前区域性进行比较

我认为关键在于顺序比较,这相当于:

顺序排序比较基于字符串的排序 关于每个字符的数值 字符串中的对象。序数 比较是自动进行的 区分大小写,因为 以及字符的大写版本 有不同的代码点。然而, 如果案例在您的工作中不重要 应用程序中,可以指定 忽略大小写的顺序比较。 这相当于将 使用 不变的文化,然后执行 对结果的顺序比较

参考资料:

使用Reflector,您可以看到以下两个方面的代码:

public bool Contains(string value)
{
    return (this.IndexOf(value, StringComparison.Ordinal) >= 0);
}

public bool StartsWith(string value, bool ignoreCase, CultureInfo culture)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (this == value)
    {
        return true;
    }
    CultureInfo info = (culture == null) ? CultureInfo.CurrentCulture : culture;
    return info.CompareInfo.IsPrefix(this, value,
        ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
}

我想出来了。这是因为它对文化敏感,而不是。这就意味着
StartsWith
必须做更多的工作

FWIW,以下是我在Mono上的结果,基准如下(更正):

1988.7906ms using Contains
10174.1019ms using StartsWith
我很高兴看到人们在MS上的结果,但我的主要观点是正确地完成了(并假设进行了类似的优化),我认为
StartsWith
必须要慢一些:

using System;
using System.Diagnostics;

public class ContainsStartsWith
{
    public static void Main()
    {
        string str = "Hello there";

        Stopwatch s = new Stopwatch();
        s.Start();
        for (int i = 0; i < 10000000; i++)
        {
            str.Contains("H");
        }
        s.Stop();
        Console.WriteLine("{0}ms using Contains", s.Elapsed.TotalMilliseconds);

        s.Reset();
        s.Start();
        for (int i = 0; i < 10000000; i++)
        {
            str.StartsWith("H");
        }
        s.Stop();
        Console.WriteLine("{0}ms using StartsWith", s.Elapsed.TotalMilliseconds);

    }
}
使用系统;
使用系统诊断;
公共类包含startswith
{
公共静态void Main()
{
string str=“你好”;
秒表s=新秒表();
s、 Start();
对于(int i=0;i<10000000;i++)
{
str.Contains(“H”);
}
s、 停止();
WriteLine(“{0}ms使用Contains”,s.appeased.total毫秒);
s、 重置();
s、 Start();
对于(int i=0;i<10000000;i++)
{
str.StartsWith(“H”);
}
s、 停止();
WriteLine(“{0}ms使用StartsWith”,s.appeased.total毫秒);
}
}

我在反射器里转来转去,找到了一个可能的答案:

包含:

return (this.IndexOf(value, StringComparison.Ordinal) >= 0);
开始于:

...
    switch (comparisonType)
    {
        case StringComparison.CurrentCulture:
            return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);

        case StringComparison.CurrentCultureIgnoreCase:
            return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);

        case StringComparison.InvariantCulture:
            return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);

        case StringComparison.InvariantCultureIgnoreCase:
            return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);

        case StringComparison.Ordinal:
            return ((this.Length >= value.Length) && (nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0));

        case StringComparison.OrdinalIgnoreCase:
            return ((this.Length >= value.Length) && (TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0));
    }
    throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
还有一些重载,因此默认区域性是CurrentCulture


所以首先,序数会更快(如果字符串接近开头),对吗?第二,这里有更多的逻辑,可能会使事情变得缓慢(尽管如此琐碎)

StartsWith
Contains
在涉及文化敏感问题时表现完全不同

特别是,
StartsWith
返回
true
并不意味着
包含
返回
true
。只有当你真的知道自己在做什么时,你才应该用另一个替换其中的一个

using System;

class Program
{
    static void Main()
    {
        var x = "A";
        var y = "A\u0640";

        Console.WriteLine(x.StartsWith(y)); // True
        Console.WriteLine(x.Contains(y)); // False
    }
}

让我们看看ILSpy对这两个人说了什么

public virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (startIndex > source.Length)
    {
        throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
    }
    if (source.Length == 0)
    {
        if (value.Length == 0)
        {
            return 0;
        }
        return -1;
    }
    else
    {
        if (startIndex < 0)
        {
            throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
        }
        if (count < 0 || startIndex > source.Length - count)
        {
            throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
        }
        if (options == CompareOptions.OrdinalIgnoreCase)
        {
            return source.IndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase);
        }
        if ((options & ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth)) != CompareOptions.None && options != CompareOptions.Ordinal)
        {
            throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
        }
        return CompareInfo.InternalFindNLSStringEx(this.m_dataHandle, this.m_handleOrigin, this.m_sortName, CompareInfo.GetNativeCompareFlags(options) | 4194304 | ((source.IsAscii() && value.IsAscii()) ? 536870912 : 0), source, count, startIndex, value, value.Length);
    }
}
public virtual int IndexOf(字符串源、字符串值、int startIndex、int计数、比较选项)
{
if(source==null)
{
抛出新的ArgumentNullException(“源”);
}
如果(值==null)
{
抛出新的ArgumentNullException(“值”);
}
如果(startIndex>source.Length)
{
抛出新ArgumentOutOfRangeException(“startIndex”,Environment.GetResourceString(“ArgumentOutOfRange_索引”);
}
if(source.Length==0)
{
如果(value.Length==0)
{
返回0;
}
返回-1;
}
其他的
{
如果(起始索引<0)
{
抛出新ArgumentOutOfRangeException(“startIndex”,Environment.GetResourceString(“ArgumentOutOfRange_索引”);
}
if(count<0 | | startIndex>source.Length-count)
{
抛出新ArgumentOutOfRangeException(“count”,Environment.GetResourceString(“ArgumentOutOfRange\u count”);
}
if(options==CompareOptions.OrdinalIgnoreCase)
{
返回source.IndexOf(value,startIndex,count,StringComparison.OrdinalIgnoreCase);
}
if((options&~(CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth))!=CompareOptions.None&&options!=CompareOptions.Ordinal)
{
抛出新的ArgumentException(Environment.GetResourceString(“Argument_InvalidFlag”),“options”);
}
返回CompareInfo.InternalFindNLStringEx(this.m_dataHandle,this.m_handleOrigin,this.m_sortName,CompareInfo.GetNativeCompareFlags(options)| 4194304 |((source.IsAscii()&value.IsAscii())?536870912:0),source,count,startIndex,value,value,value.Length);
}
}
看起来它也考虑了文化,但它是默认的

public bool StartsWith(string value, StringComparison comparisonType)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
    {
        throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
    }
    if (this == value)
    {
        return true;
    }
    if (value.Length == 0)
    {
        return true;
    }
    switch (comparisonType)
    {
    case StringComparison.CurrentCulture:
        return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
    case StringComparison.CurrentCultureIgnoreCase:
        return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
    case StringComparison.InvariantCulture:
        return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
    case StringComparison.InvariantCultureIgnoreCase:
        return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
    case StringComparison.Ordinal:
        return this.Length >= value.Length && string.nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0;
    case StringComparison.OrdinalIgnoreCase:
        return this.Length >= value.Length && TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0;
    default:
        throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
    }
public bool StartsWith(字符串值、字符串比较类型)
{
如果(值==null)
{
抛出新的ArgumentNullException(“值”);
}
if(comparisonTypeStringComparison.OrdinalIgnoreCase)
{
抛出新的ArgumentException(Environment.GetResourceString(“NotSupported_StringComparison”),“comparisonType”);
}
if(this==值)
{
返回true;
}
如果(value.Length==0)
{
返回true;
}
开关(比较类型)
{
case StringComparison.CurrentCultu
public bool StartsWith(string value, StringComparison comparisonType)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
    {
        throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
    }
    if (this == value)
    {
        return true;
    }
    if (value.Length == 0)
    {
        return true;
    }
    switch (comparisonType)
    {
    case StringComparison.CurrentCulture:
        return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
    case StringComparison.CurrentCultureIgnoreCase:
        return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
    case StringComparison.InvariantCulture:
        return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
    case StringComparison.InvariantCultureIgnoreCase:
        return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
    case StringComparison.Ordinal:
        return this.Length >= value.Length && string.nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0;
    case StringComparison.OrdinalIgnoreCase:
        return this.Length >= value.Length && TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0;
    default:
        throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
    }
|                                   Method |         Mean |      Error |       StdDev |       Median |     Gen 0 | Gen 1 | Gen 2 | Allocated |
|----------------------------------------- |-------------:|-----------:|-------------:|-------------:|----------:|------:|------:|----------:|
|                         EnumEqualsMethod |  1,079.67 us |  43.707 us |   114.373 us |  1,059.98 us | 1019.5313 |     - |     - | 4800000 B |
|                             EnumEqualsOp |     28.15 us |   0.533 us |     0.547 us |     28.34 us |         - |     - |     - |         - |
|                             ContainsName |  1,572.15 us | 152.347 us |   449.198 us |  1,639.93 us |         - |     - |     - |         - |
|                        ContainsShortName |  1,771.03 us | 103.982 us |   306.592 us |  1,749.32 us |         - |     - |     - |         - |
|                           StartsWithName | 14,511.94 us | 764.825 us | 2,255.103 us | 14,592.07 us |         - |     - |     - |         - |
|                StartsWithNameOrdinalComp |  1,147.03 us |  32.467 us |    93.674 us |  1,153.34 us |         - |     - |     - |         - |
|      StartsWithNameOrdinalCompIgnoreCase |  1,519.30 us | 134.951 us |   397.907 us |  1,264.27 us |         - |     - |     - |         - |
|                      StartsWithShortName |  7,140.82 us |  61.513 us |    51.366 us |  7,133.75 us |         - |     - |     - |       4 B |
|           StartsWithShortNameOrdinalComp |    970.83 us |  68.742 us |   202.686 us |  1,019.14 us |         - |     - |     - |         - |
| StartsWithShortNameOrdinalCompIgnoreCase |    802.22 us |  15.975 us |    32.270 us |    792.46 us |         - |     - |     - |         - |
|      EqualsSubstringOrdinalCompShortName |  4,578.37 us |  91.567 us |   231.402 us |  4,588.09 us |  679.6875 |     - |     - | 3200000 B |
|             EqualsOpShortNametoCharArray |  1,937.55 us |  53.821 us |   145.508 us |  1,901.96 us | 1695.3125 |     - |     - | 8000000 B |