【问题标题】:Implementing String.GetHashCode() manually? [duplicate]手动实现 String.GetHashCode()? [复制]
【发布时间】:2018-02-15 08:32:06
【问题描述】:

我们有一个用 .NET 编写的遗留系统,我们正在将其迁移到 Node.js。

原系统使用("some string value").GetHashCode()根据用户数据生成一些token。

我正在寻找一种在 JavaScript 中实现此功能的方法,以便将系统的这一部分移植到 Node.js。

因此,我对String.GetHashCode() 的实际工作方式很感兴趣。某处有记录的算法吗?它甚至是一种稳定的算法,还是会在各种 .NET 版本中发生变化?

我试图找到有关它的实现的一些细节,但这对我来说真的很难,因为 .NET 不是我的主要技术,而且我并不真正熟悉它的资源和信息来源。

【问题讨论】:

  • .NET 中的哈希码在不同版本之间不稳定。 Here is one implementation though, from .net core.
  • 跨版本稳定是否重要?它只是在程序运行时生成相同的值,不是吗?
  • 来自source code中的cmets,不仅跨.net版本不稳定,甚至可能在同一进程内的AppDomain之间也不稳定。
  • 你确定是 System.GetHashCode 吗?系统是一个命名空间。 GetHashCode 不带任何参数
  • 问题是以前的开发者使用这个函数来生成token,然后通过各种系统持久化和分发。此外,是否可以反转此函数的结果(他们声称,他们正在这样做),还是单向函数?

标签: .net hash


【解决方案1】:

取自微软的Reference Source,一种实现是:

        // Gets a hash code for this string.  If strings A and B are such that A.Equals(B), then
        // they will return the same hash code.
        [System.Security.SecuritySafeCritical]  // auto-generated
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public override int GetHashCode() {

#if FEATURE_RANDOMIZED_STRING_HASHING
            if(HashHelpers.s_UseRandomizedStringHashing)
            {
                return InternalMarvin32HashString(this, this.Length, 0);
            }
#endif // FEATURE_RANDOMIZED_STRING_HASHING

            unsafe {
                fixed (char *src = this) {
                    Contract.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
                    Contract.Assert( ((int)src)%4 == 0, "Managed string should start at 4 bytes boundary");

#if WIN32
                    int hash1 = (5381<<16) + 5381;
#else
                    int hash1 = 5381;
#endif
                    int hash2 = hash1;

#if WIN32
                    // 32 bit machines.
                    int* pint = (int *)src;
                    int len = this.Length;
                    while (len > 2)
                    {
                        hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
                        hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
                        pint += 2;
                        len  -= 4;
                    }

                    if (len > 0)
                    {
                        hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
                    }
#else
                    int     c;
                    char *s = src;
                    while ((c = s[0]) != 0) {
                        hash1 = ((hash1 << 5) + hash1) ^ c;
                        c = s[1];
                        if (c == 0)
                            break;
                        hash2 = ((hash2 << 5) + hash2) ^ c;
                        s += 2;
                    }
#endif
#if DEBUG
                    // We want to ensure we can change our hash function daily.
                    // This is perfectly fine as long as you don't persist the
                    // value from GetHashCode to disk or count on String A 
                    // hashing before string B.  Those are bugs in your code.
                    hash1 ^= ThisAssembly.DailyBuildNumber;
#endif
                    return hash1 + (hash2 * 1566083941);
                }
            }
        }

这在 .NET 版本之间不稳定,并且从分散在 string.cs 源代码周围的 cmets 来看,它甚至可能在同一进程内的跨 AppDomain 之间不稳定。

如果您想要一个真实、稳定的哈希码,可以“安全”地保存在 AppDomain 之外,请查看 System.Security.Cryptography 中的哈希函数。 MD5 对于低安全性工作是可以接受的,SHAx 的风格更好。

真正的哈希只是一种方法,不可能真正反转哈希,因为它是一个“有损”的过程。如果从您那里获得代码的开发人员声称他们可以反转哈希,那么他们要么是在撒谎,要么是错误的,或者没有实现正确的哈希。

【讨论】:

  • 非常感谢您发布此信息。我完全同意哈希的可逆性,从一开始我就觉得这种说法很奇怪。我在想这可能是 .NET 中的一些实现功能。
  • 如果你想要一个真实、稳定的哈希码,可以“安全”地保存在 AppDomain 之外,请查看System.Security.Cryptograph 中的哈希函数。 MD5 对于低安全性工作是可以接受的,SHAx 风格甚至更好。
  • 我不是在 .NET 中编码,我只是在移植一个已经在其中编写的服务,但感谢您的提示。
  • 另外,是否可以获得特定版本的 .NET 的参考源?我认为他们使用的是4.0 版本。
  • @BradleyUffner 我认为旧版本可能仅可用于离线访问。见referencesource.microsoft.com/download.html(或点击普通站点顶部的下载链接获取更漂亮的iframed版本)
【解决方案2】:

添加to Bradley's answer 这是一个基于String.GetHashCode() 的64 位实现的稳定哈希码,它没有使用我不久前为回答而写的不安全代码。

public static class StringExtensionMethods
{
    public static int GetStableHashCode(this string str)
    {
        unchecked
        {
            int hash1 = 5381;
            int hash2 = hash1;

            for(int i = 0; i < str.Length && str[i] != '\0'; i += 2)
            {
                hash1 = ((hash1 << 5) + hash1) ^ str[i];
                if (i == str.Length - 1 || str[i+1] == '\0')
                    break;
                hash2 = ((hash2 << 5) + hash2) ^ str[i+1];
            }

            return hash1 + (hash2*1566083941);
        }
    }
}

【讨论】:

  • 谢谢!这看起来更容易移植。
  • @SlavaFominII 刷新,我刚刚修复了一些我刚刚注意到的错误,使其更准确地匹配内置的 GetHashCode 函数。
  • 要么我的端口不正确,要么算法不同。这给了我值2d58dd33de6c6c00,但原始值是5FCF2D5
  • 这不能给你2d58dd33de6c6c00,因为那将是64位数字并且函数只返回一个32位数字。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-04
  • 1970-01-01
  • 1970-01-01
  • 2013-07-18
  • 2018-08-12
  • 2021-03-17
  • 1970-01-01
相关资源
最近更新 更多