【问题标题】:What is the fastest (built-in) comparison for string-types in C#C# 中字符串类型的最快(内置)比较是什么
【发布时间】:2010-11-29 22:30:27
【问题描述】:

C# 中最快的字符串类型内置比较方法是什么?我不介意印刷/语义含义:目的是在排序列表中使用比较器,以便在大型集合中快速搜索。我认为只有两种方法:CompareCompareOrdinal。什么速度最快?

另外,那些字符串比较有没有更快的方法?

【问题讨论】:

    标签: c# performance string


    【解决方案1】:

    我假设您想要一个小于/等于/大于的比较,而不仅仅是相等;平等是一个稍微不同的话题,尽管原则基本相同。如果您实际上只是在SortedList 之类的地方搜索存在,我会考虑改用Dictionary<string, XXX> - 您真的需要所有这些排序吗?

    String.CompareOrdinal,或使用允许提供比较的String.Compare 的重载,并指定一个序数(区分大小写)比较,例如String.Compare(x, y, StringComparison.Ordinal) 将是最快的。

    基本上,序数比较只是需要逐个字符地遍历两个字符串,直到找到差异。如果没有发现任何差异,并且长度相同,则结果为 0。如果没有发现任何差异但长度不同,则认为较长的字符串“较大”。如果它确实发现了差异,它可以立即根据哪个字符在序数中“更大”来确定哪个被认为“更大”。

    换一种说法:这就像在两个char[] 值之间进行明显的比较。

    文化敏感的比较必须执行各种曲折的壮举,具体取决于您使用的精确文化。有关这方面的示例,请参阅this question。很明显,遵循更复杂的规则会使这个过程变慢。

    【讨论】:

    • String.Compare(x, y, StringCompare.Ordinal)String.CompareOrdinal(x, y) 有区别吗?还是其中一个只是打电话给另一个?
    • @Nick:我希望一个人直接打电话给另一个人,但我不知道是哪一个。
    【解决方案2】:

    最快的是带引用相等性测试的实习字符串,但您只能进行相等性测试,而且会耗费大量内存 - 如此昂贵,几乎从来都不是推荐的课程

    除此之外,区分大小写的序数测试将是最快的,并且绝对建议将这种方法用于非文化特定的字符串。如果适用于您的用例,则区分大小写会更快。

    当您指定StringComparison.OrdinalStringComparison.OrdinalIgnoreCase 时,字符串比较将是非语言的。也就是说,在做出比较决策时会忽略特定于自然语言的特征。这意味着决策基于简单的字节比较并忽略由文化参数化的大小写或等价表。 因此,通过将参数显式设置为 StringComparison.OrdinalStringComparison.OrdinalIgnoreCase,您的代码通常会加快速度、提高正确性并变得更加可靠。

    Source

    【讨论】:

    【解决方案3】:

    我使用秒表检查了 string.Compare 和 string.CompareOrdinal

        --Compare Ordinal  case 1 
        Stopwatch sw = new Stopwatch();
        sw.Start();
        int x = string.CompareOrdinal("Jaswant Agarwal", "Jaswant Agarwal");
        sw.Stop();
        lblTimeGap.Text = sw.Elapsed.ToString(); 
    
    
    
    
    
    
        -- Only compare  case 2
        Stopwatch sw = new Stopwatch();
        sw.Start();
        int x = string.Compare("Jaswant Agarwal", "Jaswant Agarwal");
        sw.Stop();
        lblTimeGap.Text = sw.Elapsed.ToString();
    

    如果 1 平均经过的时间是 00:00:00.0000030 案例 2 平均经过的时间是 00:00:00.0000086

    我尝试了不同的 Equal 和 not equal 字符串组合,发现每次 CompareOrdinal 都比只比较快..

    这是我自己的观察..您也可以尝试在表单上放置两个按钮,然后将此代码复制粘贴到重新评分事件中..

    【讨论】:

    • 这不是一个有效的比较,因为您的样本量太小,而且您没有忽略 JIT 代码所需的时间。更好的比较是 1) 构建版本 2) 进行 10,000 次比较,3) 比较两种方法的平均值。这样,与您的计算机正在执行的 JIT 效果和背景内容相关的噪音将被最小化。
    • 完全同意@rianjs,当你处理百万分之一秒的差异时,你怎么能做到一次并说一个比另一个快?!
    【解决方案4】:

    我设计了一个单元测试来使用本文中提到的一些方法来测试字符串比较速度。该测试使用 .NET 4 运行

    简而言之,差别不大,我必须进行 100,000,000 次迭代才能看到显着差异。由于似乎是依次比较字符直到发现差异,因此不可避免地会影响字符串的相似程度。

    这些结果实际上似乎表明使用 str1.Equals(str2) 是比较字符串的最快方法。

    这些是测试的结果,包括测试类:

    ######## SET 1 compared strings are the same: 0
    #### Basic == compare: 413
    #### Equals compare: 355
    #### Equals(compare2, StringComparison.Ordinal) compare: 387
    #### String.Compare(compare1, compare2, StringComparison.Ordinal) compare: 426
    #### String.CompareOrdinal(compare1, compare2) compare: 412
    
    ######## SET 2 compared strings are NOT the same: 0
    #### Basic == compare: 710
    #### Equals compare: 733
    #### Equals(compare2, StringComparison.Ordinal) compare: 840
    #### String.Compare(compare1, compare2, StringComparison.Ordinal) compare: 987
    #### String.CompareOrdinal(compare1, compare2) compare: 776
    
    using System;
    using System.Diagnostics;
    using NUnit.Framework;
    
    namespace Fwr.UnitTests
    {
        [TestFixture]
        public class StringTests
        {
            [Test]
            public void Test_fast_string_compare()
            {
                int iterations = 100000000;
                bool result = false;
                var stopWatch = new Stopwatch();
    
                Debug.WriteLine("######## SET 1 compared strings are the same: " + stopWatch.ElapsedMilliseconds);
    
                string compare1 = "xxxxxxxxxxxxxxxxxx";
                string compare2 = "xxxxxxxxxxxxxxxxxx";
    
                // Test 1
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = compare1 == compare2;
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### Basic == compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 2
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = compare1.Equals(compare2);
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### Equals compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 3
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = compare1.Equals(compare2, StringComparison.Ordinal);
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### Equals(compare2, StringComparison.Ordinal) compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 4
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = String.Compare(compare1, compare2, StringComparison.Ordinal) != 0;
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### String.Compare(compare1, compare2, StringComparison.Ordinal) compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 5
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = String.CompareOrdinal(compare1, compare2) != 0;
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### String.CompareOrdinal(compare1, compare2) compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                Debug.WriteLine("######## SET 2 compared strings are NOT the same: " + stopWatch.ElapsedMilliseconds);
    
                compare1 = "ueoqwwnsdlkskjsowy";
                compare2 = "sakjdjsjahsdhsjdak";
    
                // Test 1
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = compare1 == compare2;
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### Basic == compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 2
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = compare1.Equals(compare2);
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### Equals compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 3
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = compare1.Equals(compare2, StringComparison.Ordinal);
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### Equals(compare2, StringComparison.Ordinal) compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 4
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = String.Compare(compare1, compare2, StringComparison.Ordinal) != 0;
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### String.Compare(compare1, compare2, StringComparison.Ordinal) compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
    
                // Test 5
    
                stopWatch.Start();
    
                for (int i = 0; i < iterations; i++)
                {
                    result = String.CompareOrdinal(compare1, compare2) != 0;
                }
    
                stopWatch.Stop();
    
                Debug.WriteLine("#### String.CompareOrdinal(compare1, compare2) compare: " + stopWatch.ElapsedMilliseconds);
    
                stopWatch.Reset();
            }
        }
    }
    

    【讨论】:

    • 这很有趣,因为默认使用序数比较。查看框架源代码,额外的时间似乎归因于参数验证。因此,出于性能考虑,您不应明确指定 StringComparison.Ordinal,只需使用默认重载即可。
    【解决方案5】:

    我刚刚注意到通过首先比较字符串长度,如果相等,然后使用 string.compare 方法,我自己的代码的性能提高了 50%。所以在一个循环中我有:

    VB:

    If strA.length = strB.length then
       if string.compare(strA,strB,true) = 0 then
          TheyAreEqual
       End if
    End if
    

    C#:

    if(strA.Length == strB.Length)
    {
       if(string.Compare(strA,strB,true) == 0)
       {
           //they are equal
       }
    }
    

    这可能取决于您自己的字符串,但它似乎对我来说效果很好。

    【讨论】:

    • 由于 C# 使用短路布尔逻辑,您不需要嵌套 if 语句 -- bool isEqual = strA.Length == strB.Length &amp;&amp; string.Compare(strA, strB, true) == 0;。 VB.NET 还支持使用 AndAlsoOrElse 运算符的短路布尔逻辑 -- Dim isEquals = strA.Length = strB.Length AndAlso String.Compare(strA, strB, true) = 0
    • 这个版本的问题是,字符串可以为空,在这种情况下,上面的代码会引发异常,因为 strA.Length
    【解决方案6】:

    这可能对某人有用,但是更改我的代码行使我的方法的单元测试从 140 毫秒缩短到 1 毫秒!

    原创

    单元测试:140ms

    public bool StringsMatch(string string1, string string2)
    {
        if (string1 == null && string2 == null) return true;
        return string1.Equals(string2, StringComparison.Ordinal);
    }
    

    单元测试:1ms

    public bool StringsMatch(string string1, string string2)
    {
        if (string1 == null && string2 == null) return true;
        return string.CompareOrdinal(string1, string2) == 0 ? true : false;
    }
    

    单元测试 (NUnit)

    [Test]
    public void StringsMatch_OnlyString1NullOrEmpty_ReturnFalse()
    {
        Authentication auth = new Authentication();
        Assert.IsFalse(auth.StringsMatch(null, "foo"));
        Assert.IsFalse(auth.StringsMatch("", "foo"));
    }
    

    有趣的是,StringsMatch_OnlyString1NullOrEmpty_ReturnFalse() 是 StringsMatch 方法中唯一需要 140 毫秒的单元测试。 StringsMatch_AllParamsNullOrEmpty_ReturnTrue() 始终为 1ms,StringsMatch_OnlyString2NullOrEmpty_ReturnFalse() 始终为

    【讨论】:

    • 运行一次测试并不是衡量性能的正确方法。声明的 140 毫秒可能是处理抛出的 NullReferenceException 的额外时间。
    【解决方案7】:

    这是一个相当古老的问题,但既然我发现了它,其他人也可能会这样做。

    在进一步研究这个主题时,我发现了一个interesting blog post,它比较了所有字符串比较的方法。可能不是很科学,但仍然是一个很好的门牌号码。

    感谢这篇文章,我开始在一个场景中使用 string.CompareOrdinal,我必须找出一个字符串是否在 170.000 个其他字符串的列表中,并且连续执行 1600 次。 string.CompareOrdinal 比 string.Equals 快了将近 50%

    【讨论】:

    • 感谢分享该链接。尽管可能不实用,但我喜欢他们尝试的“跳出框框思考”解决方案,例如将字符串加载为 Hashset 键,然后测试该键是否已经存在。 if (hs.Contains(stringsWeWantToSeeIfMatches)) {}
    【解决方案8】:

    我认为大多数 C# 开发人员都有几种比较字符串的方法,以下是最常见的:

    • Compare - 正如你所提到的
    • CompareOrdinal - 正如你所提到的
    • ==
    • String.Equals
    • 编写自定义算法以逐个字符进行比较

    如果你想走极端,你可以使用其他不那么明显的对象/方法:

    • SequenceEqual 示例:

      c1 = str1.ToCharArray(); c2 = str2.ToCharArray(); if (c1.SequenceEqual(c2))

    • IndexOf 示例:if (stringsWeAreComparingAgainst.IndexOf(stringsWeWantToSeeIfMatches, 0 , stringsWeWantToSeeIfMatches.Length) == 0)

    • 或者您可以实现 Dictionary 和 HashSet,将字符串用作“键”并测试它们是否已经与您要比较的字符串一起存在。例如:if (hs.Contains(stringsWeWantToSeeIfMatches))

    因此,您可以随意切磋琢磨,找到适合自己的做事方式。请记住,尽管有人将不得不维护代码,并且可能不想花时间试图弄清楚您为什么要使用您决定使用的任何方法。

    与往常一样,优化风险自负。 :-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-15
      • 2011-06-21
      • 2012-02-09
      • 1970-01-01
      • 2019-03-06
      相关资源
      最近更新 更多