57

引用 Eric Lippert的 GetHashCode 指南和规则:

规则:GetHashCode 的消费者不能依赖它随着时间的推移或跨应用程序域而保持稳定

假设您有一个 Customer 对象,该对象具有一堆字段,例如名称、地址等。如果您在两个不同的进程中创建两个具有完全相同数据的此类对象,则它们不必返回相同的哈希码。如果您在星期二在一个进程中创建这样的对象,然后将其关闭,然后在星期三再次运行该程序,则哈希码可能会有所不同。

这在过去曾咬人。System.String.GetHashCode 的文档特别指出,两个相同的字符串在不同版本的 CLR 中可以具有不同的哈希码,事实上它们确实如此。不要在数据库中存储字符串哈希并期望它们永远相同,因为它们不会。

那么创建可以存储在数据库中的字符串的 HashCode 的正确方法是什么?

(请告诉我,我不是第一个在我编写的软件中留下这个错误的人!)

4

3 回答 3

84

这取决于您希望该哈希具有哪些属性。例如,您可以编写如下内容:

public int HashString(string text)
{
    // TODO: Determine nullity policy.

    unchecked
    {
        int hash = 23;
        foreach (char c in text)
        {
            hash = hash * 31 + c;
        }
        return hash;
    }
}

只要您证明这是计算哈希的方式,那是有效的。它绝不是加密安全或类似的东西,但你可以毫无问题地坚持下去。在序数意义上绝对相等的两个字符串(即没有应用文化平等等,完全逐个字符相同)将使用此代码产生相同的散列。

当您依赖未记录的散列时,问题就来了 - 即遵循GetHashCode()但不能保证在版本之间保持相同的东西......比如string.GetHashCode().

像这样编写和记录您自己的哈希有点像说“这些敏感信息使用 MD5(或其他)进行哈希处理”。只要它是一个定义明确的哈希,就可以了。

编辑:其他答案建议使用加密哈希,例如 SHA-1 或 MD5。我要说的是,在我们知道需要加密安全性而不仅仅是稳定性之前,将字符串转换为字节数组并对其进行散列处理是没有意义的。当然,如果散列用于任何与安全相关的事情,那么行业标准的散列正是您应该使用的。但这在问题的任何地方都没有提到。

于 2011-03-01T13:18:21.870 回答
22

这是.NET 为 64 位系统计算其字符串哈希码的当前方式的重新实现。这不像真正的那样使用指针所以它GetHashCode()会稍微慢一些,但它确实使它对内部更改更具string弹性.

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);
        }
    }
}
于 2016-04-25T16:55:06.087 回答
-1

答案是只写你自己的散列函数。您可以通过以下链接在您发布的文章的评论中找到一些来源。或者,您可以使用最初用于加密(MD5、SHA1 等)的内置哈希函数,而不是使用所有位。

于 2011-03-01T13:18:02.193 回答