Подтвердить что ты не робот

Каков самый быстрый, нечувствительный к регистру, способ увидеть, содержит ли строка другую строку в С#?

ИЗМЕНИТЬ 2:

Подтверждено, что мои проблемы с производительностью были вызваны вызовом статической функции классу StringExtensions. После удаления метод IndexOf действительно самый быстрый способ выполнить это.

Что такое самый быстрый, нечувствительный к регистру, способ увидеть, содержит ли строка другую строку в С#? Я вижу принятое решение для сообщения здесь в нечувствительном к регистру 'Contains (string)', но я сделал предварительный бенчмаркинг, и кажется, что использование этого метода приводит к порядкам амплитуда медленнее вызывает более крупные строки ( > 100 символов) всякий раз, когда тестовая строка не может быть найдена.

Вот методы, которые я знаю:

IndexOf:

public static bool Contains(this string source, string toCheck, StringComparison comp)
{
    if (string.IsNullOrEmpty(toCheck) || string.IsNullOrEmpty(source))
        return false;

    return source.IndexOf(toCheck, comp) >= 0;
} 

ToUpper:

source.ToUpper().Contains(toCheck.ToUpper());

Regex:

bool contains = Regex.Match("StRiNG to search", "string", RegexOptions.IgnoreCase).Success;

Итак, мой вопрос в том, что действительно самый быстрый способ в среднем и почему так?

EDIT:

Вот мое простое тестовое приложение, которое я использовал, чтобы подчеркнуть разницу в производительности. Используя это, я вижу 16 мс для ToLower(), 18 мс для ToUpper и 140 мс для StringExtensions.Contains():

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace ScratchConsole
{
    class Program
    {
    static void Main(string[] args)
    {
        string input = "";
        while (input != "exit")
        {
            RunTest();
            input = Console.ReadLine();
        }
    }

    static void RunTest()
    {
        List<string> s = new List<string>();
        string containsString = "1";
        bool found;
        DateTime now;
        for (int i = 0; i < 50000; i++)
        {
            s.Add("AAAAAAAAAAAAAAAA AAAAAAAAAAAA");
        }

        now = DateTime.Now;
        foreach (string st in s)
        {
            found = st.ToLower().Contains(containsString);
        }
        Console.WriteLine("ToLower(): " + (DateTime.Now - now).TotalMilliseconds);

        now = DateTime.Now;
        foreach (string st in s)
        {
            found = st.ToUpper().Contains(containsString);
        }
        Console.WriteLine("ToUpper(): " + (DateTime.Now - now).TotalMilliseconds);


        now = DateTime.Now;
        foreach (string st in s)
        {
            found = StringExtensions.Contains(st, containsString, StringComparison.OrdinalIgnoreCase);
        }
        Console.WriteLine("StringExtensions.Contains(): " + (DateTime.Now - now).TotalMilliseconds);

    }
}

public static class StringExtensions
{
    public static bool Contains(this string source, string toCheck, StringComparison comp)
    {
        return source.IndexOf(toCheck, comp) >= 0;
    }
}

}

4b9b3361

Ответ 1

Так как ToUpper на самом деле приведет к созданию новой строки, StringComparison.OrdinalIgnoreCase будет быстрее, также у regex есть много накладных расходов для простого сравнения, подобного этому. Тем не менее, String.IndexOf(String, StringComparison.OrdinalIgnoreCase) должен быть самым быстрым, поскольку он не требует создания новых строк.

Я бы предположил (там я еще раз), что RegEx имеет лучший худший случай из-за того, как он оценивает строку, IndexOf всегда будет выполнять линейный поиск, я предполагаю (и снова), что RegEx немного что-то использует лучше. RegEx также должен иметь лучший вариант, который, вероятно, будет близким, хотя и не таким хорошим, как IndexOf (из-за дополнительной сложности на нем).

15,000 length string, 10,000 loop

00:00:00.0156251 IndexOf-OrdinalIgnoreCase
00:00:00.1093757 RegEx-IgnoreCase 
00:00:00.9531311 IndexOf-ToUpper 
00:00:00.9531311 IndexOf-ToLower

Placement in the string also makes a huge difference:

At start:
00:00:00.6250040 Match
00:00:00.0156251 IndexOf
00:00:00.9687562 ToUpper
00:00:01.0000064 ToLower

At End:
00:00:00.5781287 Match
00:00:01.0468817 IndexOf
00:00:01.4062590 ToUpper
00:00:01.4218841 ToLower

Not Found:
00:00:00.5625036 Match
00:00:01.0000064 IndexOf
00:00:01.3750088 ToUpper
00:00:01.3906339 ToLower

Ответ 2

Я обнаружил, что скомпилированный RegEx является самым быстрым решением и, очевидно, гораздо более универсальным. Компиляция его помогает поставить ее на равных с меньшими сравнениями строк, и, как вы заявили, сравнение с большими строками отсутствует.

http://www.dijksterhuis.org/regular-expressions-advanced/ содержит некоторые подсказки, чтобы получить максимальную скорость от сравнений RegEx; вам может показаться полезным.

Ответ 3

Это был интересный вопрос для меня, поэтому я создал небольшой тест, используя разные методы.

string content = "";
            for (var i = 0; i < 10000; i++)
                content = String.Format("{0} asldkfjalskdfjlaskdfjalskdfj laksdf lkwiuirh 9238 r9849r8 49834", content);

            string test = String.Format("{0} find_me {0}", content);

            string search = test;

            var tickStart = DateTime.Now.Ticks;
            //6ms
            //var b = search.ToUpper().Contains("find_me".ToUpper());

            //2ms
            //Match m = Regex.Match(search, "find_me", RegexOptions.IgnoreCase);


            //a little bit over 1ms
            var c = false;
            if (search.Length == search.ToUpper().Replace("find_me".ToUpper(), "x").Length)
                c = true;
            var tickEnd = DateTime.Now.Ticks;
            Debug.Write(String.Format("{0} {1}", tickStart, tickEnd));

Итак, что я сделал, создается строка и поиск в ней

первый метод search.ToUpper().Contains("find_me".ToUpper()) 5ms

второй метод Match m = Regex.Match(search, "find_me", RegexOptions.IgnoreCase) 2ms

третий метод

if (search.Length == search.ToUpper().Replace("find_me".ToUpper(), "x").Length)
                c = true;

потребовалось не более 1 мс