问题 查看字符串是否包含C#中的另一个字符串的最快,不区分大小写的方法是什么?


编辑2:

确认我的性能问题是由对StringExtensions类的静态函数调用引起的。一旦删除,IndexOf方法确实是实现此目的的最快方法。

查看字符串是否包含C#中的另一个字符串的最快,不区分大小写的方法是什么?我在这里看到了这个帖子的公认解决方案 不区分大小写'包含(字符串)' 但我已经做了一些初步的基准测试,似乎使用该方法会导致在无法找到测试字符串时对较大字符串(> 100个字符)的调用速度减慢。

以下是我所知道的方法:

指数:

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

正则表达式:

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

所以我的问题是,这实际上是平均最快的方式,为什么呢?

编辑:

这是我用来突出性能差异的简单测试应用程序。使用它,我看到ToLower()为16 ms,ToUpper为18 ms,StringExtensions.Contains()为140 ms:

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

}


10174
2017-10-13 20:13


起源

这对您来说是一个真正的性能瓶颈吗? - Oded
您是否在发布模式下进行基准测试,并在抖动预热后进行优化,并且没有附加调试器,并且过度迭代以实质上证明存在差异? - Anthony Pegram
您需要注意您正在使用的区域设置。比较规则因文化而异。例如 STRING 和 string 使用土耳其文化时不匹配。 - CodesInChaos
@hspain:首先,你没有足够长时间的基准测试,并且认为你正在使用它 DateTime.Now这不是一个高分辨率的计时器。你应该用 Stopwatch 代替。 - Jon Skeet
这些家伙是对的 - 你需要非常小心地决定你正在使用的区分大小写的规则。 在快速获取代码之前获取正确的代码。 - Eric Lippert


答案:


由于ToUpper实际上会导致创建一个新字符串,因此StringComparison.OrdinalIgnoreCase会更快,同样,正则表达式对于像这样的简单比较有很多开销。也就是说,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

14
2017-10-13 20:18



没有评论的下降率就像没有狗的遛狗一样...... - aepheus
我想我应该说“我99%肯定,但是当提问的人应该”时,不会花时间进行剖析。“ - aepheus
我在问题中链接到同一篇文章。另外,我注意到性能存在显着差异,我正在寻找原因的答案。 - hspain
感谢您发布您的号码。找到字符串时,我得到类似的数字。但是,如果找不到字符串,您会得到类似的结果吗?这是我得到性能数反转的地方。 - hspain
找不到总是最糟糕的情况,也会运行和发布。 - aepheus


我发现编译后的RegEx是迄今为止最快的解决方案,显然更加通用。编译它有助于使其与较小的字符串比较相提并论,如您所述,没有与较大的字符串进行比较。

http://www.dijksterhuis.org/regular-expressions-advanced/ 包含一些提示,以便从RegEx比较中获得最大速度;你会发现它很有帮助。


1
2017-10-13 20:20



我发现了同样的事情。在500 MB的数据集上,string.IndexOf(Ordinal.IgnoreCase)需要12秒,但等效编译的Regex需要2到4秒(取决于比较字符串的长度)。 - Bryce Wagner


这对我来说很有趣,所以我用不同的方法创建了一个小测试

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毫秒


0
2017-10-13 20:44