【问题标题】:Is String.Equals(string1.Substring(0, x), string2) better than string1.StartsWith(string2)?String.Equals(string1.Substring(0, x), string2) 是否优于 string1.StartsWith(string2)?
【发布时间】:2012-01-15 05:21:06
【问题描述】:

我正在使用字符串比较来测试使用 StringComparison.OrdinalIgnoreCase 的 URL 路径。

MSDN 给出了以下字符串比较建议HERE,但没有说明WHY

MSDN 示例(上一页的一半):

public static bool IsFileURI(string path) 
{
   path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
   return true;
}

MSDN 建议:

"但是,前面的示例使用 String.StartsWith(String, StringComparison) 方法来测试是否相等。因为比较的目的是测试是否相等而不是对字符串进行排序,所以更好的替代方法是调用 Equals方法,如下例所示。”

public static bool IsFileURI(string path)
{
   if (path.Length < 5) return false;

   return String.Equals(path.Substring(0, 5), "FILE:", 
                    StringComparison.OrdinalIgnoreCase);
}

问题:为什么 MSDN 建议第二个示例更好?

讨论点:

  1. 很明显,第一个示例中的return true; 是一个错误,应该是return path.StartsWith(...);由于 VB 代码是正确的,我们可以放心地忽略这个错误。

  2. 在比较相等性之前创建子字符串似乎只使用另一个内存资源,而不仅仅是调用 String.StartsWith()。

  3. Length

  4. 第二个示例可以解释为更清晰的代码,但我关心的是性能。子字符串的创建似乎没有必要。

【问题讨论】:

  • 基于StartsWith 示例函数没有意义的事实,我将采用该示例最初使用不同函数(如CompareTo)的想法,并且有人改变了不注意上下文就随意采样。
  • 我发现“startswith”在表达你的意图时要清晰得多。
  • @Gabe 你能就一般性问题发表意见吗?使用 StartsWith 似乎对我来说更可取。为比较创建一个子字符串似乎效果不太好。
  • @I82Much - 我同意,在我看来,使用StartsWith 的代码更简洁(一旦修复了其中的错误)。我认为这里的可能性是 1) 微软说 StartsWith 已完全损坏,不应该被使用,或者 2) 有人对旧/预先存在的文章进行了粗心的修改,以这样的方式更改它不再有意义。
  • @KevinR:StartsWith 方法通常更可取,因为它们最终都会调用相同的内部函数InternalCompareStringOrdinalIgnoreCase。唯一真正的区别是Equals 有一个优化的情况,其中两个字符串都是 ASCII。

标签: c# .net


【解决方案1】:

查看使用dotPeek的StartsWith方法,它最终调用了一个内部比较函数来比较整个字符串,并根据该比较的返回值返回一个布尔结果:

return TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0;

String.Equals 来电:

return TextInfo.CompareOrdinalIgnoreCase(this, value) == 0;

CompareOrdinalIgnoreCase 调用了一个私有方法,dotPeek 没有显示,但我的直觉是StartsWith 调用的重载遍历整个字符串,而Equals 调用的重载在确定相等后立即停止.

如果性能是一个问题,请尝试使用您的应用程序的典型值来衡量两者。


出于好奇,我尝试测量这两者,似乎Equals 明显更快。当我使用发布版本运行以下代码时,Equals 的速度几乎是StartsWith 的两倍:

using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var url = "http://stackoverflow.com/questions/8867710/is-string-equalsstring1-substring0-x-string2-better-than-string1-startswit";
            var count = 10000000;
            var http = false;

            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < count; i++)
            {
                http = url.StartsWith("http:", StringComparison.OrdinalIgnoreCase);
            }

            sw.Stop();

            Console.WriteLine("StartsWith: {0} ms", sw.ElapsedMilliseconds);

            sw.Restart();

            for (int i = 0; i < count; i++)
            {
                http = string.Equals(url.Substring(0, 5), "http:", StringComparison.OrdinalIgnoreCase);
            }

            sw.Stop();

            Console.WriteLine("Equals: {0} ms", sw.ElapsedMilliseconds);

            Console.ReadLine();
        }
    }
}

【讨论】:

  • 谢谢!我无法想象为什么StartsWith 会继续遍历超出其参数长度的整个字符串。同样,Equals 在两个字符串都完全遍历之前无法确定相等性,除非更快地发现不等式。
  • @KevinR:不,StartsWith 函数内部只遍历前缀的长度。
  • @adrift:Equals 更快的原因很可能是因为您要比较的字符串都是 ASCII(在 String.Equals 中具有优化的大小写,但在 StartsWith 中没有)。如果您在前 5 个字符中使用单个非 ASCII 字符,您应该会看到速度优势消失了。
  • @KevinR:在我的电脑上,Substring 在大约 16 个字符后开始使 Equals 方法变慢。
  • @KevinR,很高兴我能提供帮助,感谢您提出的有趣问题。
【解决方案2】:

答案就在您在Ordinal String Operations 标题下提供的代码示例下方。

.NET Framework 中的字符串可以包含嵌入的空字符。 序数和文化敏感之间最明显的区别之一 比较(包括使用不变区域性的比较) 涉及处理字符串中嵌入的空字符。这些 使用 String.Compare 和 执行文化敏感比较的 String.Equals 方法 (包括使用不变区域性的比较)。因此, 在文化敏感的比较中,包含嵌入 null 的字符串 字符可以被认为等于不等于的字符串。

重要

虽然字符串比较方法忽略嵌入的 null 字符,字符串搜索方法,例如 String.Contains, String.EndsWith、String.IndexOf、String.LastIndexOf 和 String.StartsWith 不要。

意味着使用序数比较的String.StartsWithString.Equals 将返回不同的结果。也就是说,使用序数比较的全部目的是防止土耳其系统上的人们绕过安全问题(忽略大小写时"f" != "F")。这意味着如果测试使用String.StartsWith,则有人仍然可以通过传递看起来像"FIL\0E:" 的文件URI 来规避安全问题

【讨论】:

  • 不,我认为您误读了这两段:“...当您使用...方法执行文化敏感比较时,这些字符 [nulls] 将被忽略”。使用 StringComparison.Ordinal 不是文化敏感的比较。此外,“...字符串搜索方法,例如...StartsWith DO NOT [忽略空字符]”。空值问题出现在CompareEquals,但不是StartsWith。使用Ordinal 比较时,两者都不会忽略空值。这是我的看法,我的测试似乎支持它。感谢您指出 Ordinal 比较的重要性以及任何其他想法。
  • @KevinR 看起来你是对的。我最初是在阅读其他重要说明之前阅读的,这可能是我困惑的根源。
  • 很容易混淆,这就是为什么我要求对此进行更敏锐的观察。我认为漂流和加布启发了正确的细节。最好的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-09
  • 2014-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多