Содержит быстрее чем StartsWith? - PullRequest
29 голосов
/ 25 июня 2010

Вчера пришел консультант, и почему-то возникла тема о строках. Он упомянул, что заметил, что для строк меньше определенной длины, Contains на самом деле быстрее, чем StartsWith. Я должен был увидеть это своими собственными глазами, поэтому я написал небольшое приложение и, конечно же, Contains быстрее!

Как это возможно?

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

Выходы:

726ms using Contains 
865ms using StartsWith

Я пробовал и с более длинными строками!

Ответы [ 5 ]

25 голосов
/ 25 июня 2010

Попробуйте использовать StopWatch для измерения скорости вместо DateTime проверки.

Секундомер против использования System.DateTime.Now для синхронизации событий

Я думаю, что ключом являются следующие важные части, выделенные жирным шрифтом:

Contains:

Этот метод выполняет порядковый номер (с учетом регистра и нечувствительное к культуре сравнение ).

StartsWith:

Этот метод выполняет слово (с учетом регистра и с учетом культуры ) сравнение с использованием текущей культуры.

Я думаю, что ключом является порядковое сравнение , которое составляет:

Порядковый тип сортировки сравнивает строки на числовое значение каждого символа объект в строке. Порядковый номер сравнение автоматически чувствителен к регистру, потому что строчные и заглавные версии персонажа имеют разные кодовые точки. Тем не мение, если дело не важно в вашем приложение, вы можете указать порядковое сравнение, которое игнорирует регистр. Это эквивалентно преобразованию строка в верхний регистр, используя инвариантная культура, а затем выполнение порядковое сравнение по результату.

Ссылки:

http://msdn.microsoft.com/en-us/library/system.string.aspx

http://msdn.microsoft.com/en-us/library/dy85x1sa.aspx

http://msdn.microsoft.com/en-us/library/baketfxw.aspx

С помощью 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);
}
23 голосов
/ 25 июня 2010

Я понял это.Это потому, что StartsWith чувствителен к культуре, а Contains - нет.Это по сути означает, что 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);

    }
}
9 голосов
/ 03 мая 2013

StartsWith и Contains ведут себя совершенно по-разному, когда дело касается чувствительных к культуре проблем.

В частности, StartsWith возврат true НЕ означает Contains возврат 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
    }
}
3 голосов
/ 25 июня 2010

Я перевернулся в Reflector и нашел потенциальный ответ:

Содержит:

return (this.IndexOf(value, StringComparison.Ordinal) >= 0);

StartsWith:

...
    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.

Итак, во-первых, Ordinal будет быстрее (если строка близка к началу), верно?А во-вторых, здесь есть больше логики, которая может замедлить ход событий (хотя это так тривиально)

0 голосов
/ 03 мая 2013

Давайте посмотрим, что 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);
    }
}

Похоже, что он также учитывает культуру, но не исполняется.Я вижу, что это актуально, это проверка дополнительной длины.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...