C#对象的32位散列函数

时间:2013-04-18 22:55:28

标签: c# hash gethashcode

我希望在所有类中覆盖对象的GetHashCode()方法。此方法返回Int32。我知道所有加密哈希函数的返回值都不适合32位整数。我想尽可能避免碰撞。我应该截断像SHA一样的安全散列,还是使用32位散列?如果使用32位散列,那么最好的32位散列是什么?

4 个答案:

答案 0 :(得分:7)

向所有人提供一些信息。不同.NET平台上的GetHashCode()有所不同。例如:.NET 2.0中的“Hello”.GetHashCode()与.NET 4.0中的“Hello”.GetHashCode()会产生不同的结果。因此,为什么你不能使用.NET来开箱即用地序列化HashTables或Dictionaries。

实现自己的哈希算法可以跨平台提供一致性。只是为了让你知道,你不想低于Int32。我的建议是坚持使用Int64(长)。这样你就可以减少冲突,这是散列的目标:)这是我多年前写的一个库。每个哈希算法都有它们的优缺点(速度与最小碰撞)。此特定版本使用字符串作为输入,但您可以根据需要修改它:

static public class StringHash
    {
        //---------------------------------------------------------------------
        static public Int64 RSHash(String str)
        {
            const Int32 b = 378551;
            Int32 a = 63689;
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = hash * a + str[i];
                a = a * b;
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 JSHash(String str)
        {
            Int64 hash = 1315423911;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash ^= ((hash << 5) + str[i] + (hash >> 2));
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 ELFHash(String str)
        {
            Int64 hash = 0;
            Int64 x = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = (hash << 4) + str[i];

                if ((x = hash & 0xF0000000L) != 0)
                {
                    hash ^= (x >> 24);
                }
                hash &= ~x;
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 BKDRHash(String str)
        {
            const Int64 seed = 131; // 31 131 1313 13131 131313 etc..
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = (hash * seed) + str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 SDBMHash(String str)
        {
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = str[i] + (hash << 6) + (hash << 16) - hash;
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 DJBHash(String str)
        {
            Int64 hash = 5381;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = ((hash << 5) + hash) + str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 DEKHash(String str)
        {
            Int64 hash = str.Length;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = ((hash << 5) ^ (hash >> 27)) ^ str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 BPHash(String str)
        {
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = hash << 7 ^ str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 FNVHash(String str)
        {
            Int64 fnv_prime = 0x811C9DC5;
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash *= fnv_prime;
                hash ^= str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 APHash(String str)
        {
            Int64 hash = 0xAAAAAAAA;

            for (Int32 i = 0; i < str.Length; i++)
            {
                if ((i & 1) == 0)
                {
                    hash ^= ((hash << 7) ^ str[i] * (hash >> 3));
                }
                else
                {
                    hash ^= (~((hash << 11) + str[i] ^ (hash >> 5)));
                }
            }

            return hash;
        }
    }

答案 1 :(得分:2)

可以通过截断SHA哈希来实现GetHashCode。但你可能不应该这样做。

GetHashCode的目的是允许将对象插入hash tables。散列表的目的是优化搜索:平均而言,在散列表中查找密钥只需要O(1)时间,而树的O(log n)或未排序列表的O(n)。 / p>

您确实希望GetHashCode方法最小化冲突,以防止哈希表查找退化为O(n)时间。但是你也希望它们快速,因为哈希表的重点是优化。如果您的哈希代码需要很长时间才能计算,那么您可能只是将数据存储在List中。

加密哈希很慢。它们通常是设计的,以阻止暴力攻击。这使得它们不适合与GetHashCode一起使用。

那么应该如何实施GetHashCode?一种简单的,经常使用的方法就是将Equals函数中使用的所有成员变量进行异或运算。

struct Complex
{
    double real;
    double imag;

    public override int GetHashCode()
    {
        return real.GetHashCode() ^ imag.GetHashCode();
    }

    // ...
}

另一种适用于类似数组的对象的简单方法是polynomial hash function

class MyClass
{
    int[] data;

    public override int GetHashCode()
    {
        int result = 0;

        foreach (int n in data)
        {
            result = result * 41 + n;
        }

        return result;
    }

    // ...
}

如果您的类包含大量要散列的数据,您可能希望将哈希代码保存在成员变量中并在构造期间预先计算它,以便GetHashCode()可以只使用该变量。

答案 2 :(得分:2)

Eric Lippert创建了一个关于如何正确实现GetHashCode()方法的great blog entry。您需要记住,GetHashCode()的目的是将对象放入哈希表中。将它用于此目的意味着您可能希望在将来的某个时间迭代它或进行排序。如果您使用加密函数来执行此操作,则迭代或排序过程将非常慢。加密函数旨在保护数据,而不是唯一地识别它们。阅读Eric Lippert的博客文章。它会帮助你

答案 3 :(得分:0)

哈希值的宽度越短,就越有可能发生冲突。由于Int32存储了最多4294967296个不同的值,因此您需要考虑这是否会为您的目的保留足够的唯一值 - 这取决于是否用于安全性或身份检查。

我对你想要覆盖GetHashCode()的原因感兴趣,这个值是否必须适合32位?如果是这样的话?