【问题标题】:Can I depend on the values of GetHashCode() to be consistent?我可以依赖 GetHashCode() 的值来保持一致吗?
【发布时间】:2010-09-08 08:39:06
【问题描述】:

假设使用相同的字符串值,GetHashCode() 的返回值是否保证一致? (C#/ASP.NET)

我今天将我的代码上传到服务器,令我惊讶的是,我不得不重新索引一些数据,因为我的服务器(win2008 64 位)与我的台式计算机相比返回了不同的值。

【问题讨论】:

    标签: c# hash


    【解决方案1】:

    如果我没记错的话,GetHashCode 在给定相同值的情况下是一致的,但不能保证在不同版本的框架之间保持一致。

    来自关于 String.GetHashCode() 的 MSDN 文档:

    GetHashCode 的行为取决于它的实现,它可能会从公共语言运行时的一个版本更改为另一个版本。发生这种情况的一个原因是为了提高 GetHashCode 的性能。

    【讨论】:

    • 结论:永远不要持久化或传输GetHashCode()的结果。仅将其用于预期目的:促进哈希表的使用。
    【解决方案2】:

    我遇到了类似的问题,我在数据库表中填充了依赖于 String.GetHashCode 的信息(不是最好的主意),当我将正在使用的服务器升级到 x64 时,我注意到我从 String 获得的值.GetHashCode 与表中已有的内容不一致。我的解决方案是使用我自己的 GetHashCode 版本,它在 x86 框架上返回与 String.GetHashCode 相同的值。

    这是代码,别忘了用“允许不安全代码”编译:

        /// <summary>
        /// Similar to String.GetHashCode but returns the same as the x86 version of String.GetHashCode for x64 and x86 frameworks.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static unsafe int GetHashCode32(string s)
        {
            fixed (char* str = s.ToCharArray())
            {
                char* chPtr = str;
                int num = 0x15051505;
                int num2 = num;
                int* numPtr = (int*)chPtr;
                for (int i = s.Length; i > 0; i -= 4)
                {
                    num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
                    if (i <= 2)
                    {
                        break;
                    }
                    num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
                    numPtr += 2;
                }
                return (num + (num2 * 0x5d588b65));
            }
        }
    

    【讨论】:

    【解决方案3】:

    实现取决于框架的版本,但也取决于architecture。 string.GetHashCode() 的实现在 x86 和 x64 版本的框架中是不同的,即使它们具有相同的版本号。

    【讨论】:

      【解决方案4】:

      我想知道 32 位和 64 位操作系统之间是否存在差异,因为我确定我的服务器和家用计算机都运行相同版本的 .NET

      我一直对使用 GetHashCode() 感到厌烦,对我来说,简单地扮演我自己的哈希算法可能是个好主意。好吧,至少因为它,我最终写了一个快速重新索引 .aspx 页面。

      【讨论】:

        【解决方案5】:

        您是否正在运行 Win2008 x86 作为您的桌面?因为 Win2008 包含版本2.0.50727.1434,它是 Vista RTM 中包含的 2.0 的更新版本。

        【讨论】:

          【解决方案6】:

          然而,我们确实注意到了,当 对象在散列集合中 对象(哈希表,字典 等),当 2 个对象不是唯一的 但他们的哈希码是,哈希码 仅用作第一个选项查找, 如果有非唯一的哈希码 正在使用,相等运算符是 总是用作回退到 确定平等。

          这就是哈希查找的工作方式,对吧?每个桶包含一个具有相同哈希码的项目列表。

          为了在这些条件下找到正确的项目,使用值相等比较进行线性搜索。

          如果您的散列实现实现了良好的分布,则不需要此搜索,即每个存储桶一个项目。

          我的理解正确吗?

          【讨论】:

          • 本,从我们的测试来看,这是真的。第二个相等搜索仅在需要时运行。您可以通过重载某个对象的 ==、!=、Equals() 和 GetHashCode 来自己测试它。我发现它很有趣(但我是个极客:))
          • (续),因此非唯一哈希码的连锁效应会降低运行相等性检查的性能,但在非唯一值非常罕见的情况下,它在很大程度上是微不足道的
          【解决方案7】:

          不是直接回答您的问题,Jonas 已经很好地回答了这个问题,但是如果您担心哈希中的相等性测试,这可能会有所帮助

          根据我们的测试,根据您对哈希码的要求,在 C# 中,对于 Equality 操作,哈希码不需要是唯一的。例如,考虑以下情况:

          我们需要重载等号运算符,因此我们的对象的 GetHashCode 函数已经变得易变和无状态,并且直接从数据中获取自身,因此我们需要在应用程序的一个地方确保一个对象将被视为等同于另一个对象如果它来自相同的数据,而不仅仅是如果它是相同的引用。我们的唯一数据标识符是 Guid。

          equals 运算符很容易满足,因为我们刚刚检查了记录的 Guid(在检查 null 之后)。

          不幸的是,HashCode 数据大小(作为 int)取决于操作系统,而在我们的 32 位系统上,哈希码将是 32 位。从数学上讲,当我们重写 GetHashCode 函数时,不可能从大于 32 位的 guid 生成唯一的哈希码(从反面来看,如何将 32 位整数转换为 guid?)。

          然后我们进行了一些测试,将 Guid 作为字符串并返回 Guid 的 HashCode,它在我们的测试中几乎总是返回唯一标识符,但并非总是如此。

          然而,我们确实注意到,当一个对象在一个散列集合对象(散列表、字典等)中时,当 2 个对象不是唯一的但它们的散列码是唯一的时,散列码仅用作第一个选项查找,如果使用了非唯一的哈希码,相等运算符始终用作确定相等性的后备

          正如我所说,这可能与您的情况相关,也可能不相关,但如果它是一个方便的提示。

          更新

          为了演示,我们有一个 Hashtable:

          键:对象A(哈希码1),值对象A1

          键:对象B(哈希码1),值对象B1

          键:对象C(哈希码1),值对象C1

          键:对象 D(哈希码 2),值对象 D1

          键:对象 E(哈希码 3),值对象 E1

          当我使用对象 A 的键调用对象的哈希表时,对象 A1 将在 2 个步骤后返回,调用哈希码 1,然后对键对象进行相等性检查,因为没有唯一键哈希码 1

          当我用对象 D 的键调用对象的哈希表时,对象 D1 将在 1 步后返回,一个哈希查找

          【讨论】:

            【解决方案8】:
                /// <summary>
                /// Default implementation of string.GetHashCode is not consistent on different platforms (x32/x64 which is our case) and frameworks. 
                /// FNV-1a - (Fowler/Noll/Vo) is a fast, consistent, non-cryptographic hash algorithm with good dispersion. (see http://isthe.com/chongo/tech/comp/fnv/#FNV-1a)
                /// </summary>
                private static int GetFNV1aHashCode(string str)
                {
                    if (str == null)
                        return 0;
                    var length = str.Length;
                    // original FNV-1a has 32 bit offset_basis = 2166136261 but length gives a bit better dispersion (2%) for our case where all the strings are equal length, for example: "3EC0FFFF01ECD9C4001B01E2A707"
                    int hash = length;
                    for (int i = 0; i != length; ++i)
                        hash = (hash ^ str[i]) * 16777619;
                    return hash;
                }
            

            此实现可能比之前发布的不安全实现要慢。但更简单、更安全。

            【讨论】:

            • 这是 FNV1a,与 GetHashCode 中使用的算法完全不同,这不是 OP 所要求的。
            • 这是不同的,但它是解决 OP 遇到的问题的简单、有用的解决方案 +1
            【解决方案9】:

            我不得不说...你不能依赖它。例如,如果我通过 c# 的 md5 哈希码运行 file1 并将相同的文件复制并粘贴到一个新目录中......哈希码会出现不同的情况,即使它是同一个文件。显然它是相同的 .net 版本,相同的一切。唯一改变的是路径。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-01-23
              • 2021-10-03
              • 1970-01-01
              • 2010-12-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-05-29
              相关资源
              最近更新 更多