快速创建32位哈希码,唯一标识由(大部分)原始值组成的结构

时间:2012-04-26 20:03:31

标签: c# .net hash bitarray bitvector

编辑:64或128位也可以。由于某种原因,我的大脑刚刚跳到32位,认为这就足够了。

我有一个结构,主要由数值(int,decimal)和3个字符串组成,每个字符串不超过12个字母。我正在尝试创建一个整数值,它将作为哈希码,并尝试快速创建它。一些数值也可以为空。

BitVector32或BitArray似乎是在这个项目中使用的有用实体,但我只是不确定如何在这项任务中弯曲它们。我的结构包含3个字符串,12个小数(其中7个可以为空)和4个整数。

为了简化我的用例,假设你有以下结构:

public struct Foo
{
    public decimal MyDecimal;
    public int? MyInt;
    public string Text;
}

我知道我可以为每个值获取数字标识符。从数字的角度来看,MyDecimal和MyInt当然是独一无二的。该字符串有一个GetHashCode()函数,它将返回一个通常唯一的值。

因此,使用每个数字标识符,是否可以生成唯一标识此结构的哈希码?例如我可以比较包含相同值的2个不同的Foo,并且每次都获得相同的哈希码(无论应用域,重新启动应用,时间,木星卫星对齐等)。

哈希是稀疏的,所以我不预期我的用例会发生冲突。

有什么想法吗?我第一次运行它时,我将所有内容转换为字符串表示形式,对其进行了汇总,并使用了内置的GetHashCode(),但这似乎非常低效。

编辑:更多背景信息。结构数据被传递给web客户端,客户端对包含的值,字符串构造等进行大量计算以重新呈现页面。上述19字段结构表示单个信息单元,每个页面可以具有许多单元。我想对渲染结果进行一些客户端缓存,因此如果我从服务器看到相同的哈希标识符,我可以快速重新渲染单元而无需在客户端重新计算。 JavaScript数值都是64位,所以我认为我的32位约束是人为的和限制性的。 64位可以工作,或者我想甚至128位,如果我可以在服务器上将它分成两个64位值。

5 个答案:

答案 0 :(得分:3)

那么,即使在稀疏表中,也应该更好地准备碰撞,这取决于“稀疏”的含义。

Hash collisions probability (uniform distribution)

你需要能够同时对你将要散列的数据进行非常的假设,以便用32位来打败这个图。

使用SHA256。您的哈希值不依赖于CLR版本,您将不会发生冲突。那么,你仍然会有一些,但不像陨石撞击那么频繁,所以你可以负担不起任何预期。

答案 1 :(得分:1)

我建议你看两件事herehere。我认为你不能保证只有32位的冲突。

答案 2 :(得分:1)

根据哈希函数的定义,哈希码并不是唯一的。它们仅用于尽可能均匀地分布在所有结果值上。获取对象的哈希码意味着快速方式来检查两个对象是否不同。如果两个对象的哈希码不同,那么这些对象是不同的。但是如果哈希码是相同的,你必须深入比较对象。散列码的主要用途是在所有基于散列的集合中,它们可以实现几乎O(1)的检索速度。

所以从这个角度来看,你的GetHashCode并不一定非常复杂,事实上它不应该。它必须在非常快速和产生均匀分布的值之间取得平衡。如果获得哈希码需要很长时间,那么它就会变得毫无意义,因为深度比较的优势已经消失。如果在另一个极端,哈希码总是1例如(快速点亮)它将导致在每种情况下的深度比较,这使得这个哈希码也没有意义。

所以要保持正确的平衡,不要试图想出一个完美的哈希码。在所有(或大多数)成员上调用GetHashCode,并使用Xor运算符将结果合并为按位移位运算符<<>>。框架类型GetHashCode已经完全优化,尽管在每个应用程序运行中都不能保证它们相同。没有保证,但他们也没有必要改变,很多人没有。使用反射器根据反射的代码确定或创建自己的版本。

在您的特定情况下,通过查看其哈希码来确定您是否已经处理了一个结构,这有点冒险。散列越好风险越小但仍然如此。最终且唯一的唯一哈希码是......数据本身。使用哈希码时,您还必须覆盖Object.Equals,以使您的代码真正可靠。

答案 3 :(得分:0)

我相信.NET中常用的方法是在结构的每个成员上调用GetHashCode并对结果进行xor。

但是,我不认为GetHashCode声称在不同的应用程序域中为相同的值生成相同的哈希值。

您是否可以在问题中提供更多信息,说明您为何需要此哈希值以及为什么需要在一段时间内保持稳定,不同的应用域等等。

答案 4 :(得分:0)

你追求的目标是什么?如果它是性能,那么你应该使用一个类,因为每当你将结构作为函数参数传递时,结构将被值复制。

  

3个字符串,12个小数(其中7个可以为空)和4个整数。

在64位机器上,指针的大小为8个字节,十进制为16个字节,int为4个字节。忽略填充结构将使用每个实例232字节。与推荐的最大16个字节相比,这要大得多,因为它的对象标题,类占用了至少16个字节,...)

如果您需要值的指纹,您可以使用加密级哈希算法,如SHA256,它将产生16字节指纹。这仍然不是独一无二的,但至少是足够独特的。但这也会花费相当多的性能。

<强> EDIT1: 在您明确表示需要哈希码来识别Java Script Web客户端缓存中的对象之后,我很困惑。为什么服务器再次发送相同的数据?使服务器更智能,只发送客户端尚未收到的数据会不会更简单?

在您的情况下,SHA哈希算法可以创建一些对象实例标记。


为什么你需要一个哈希码呢?如果您的目标是以内存有效的方式存储值,则可以创建一个FooList,它使用字典仅存储相同的值一次,并使用和int作为查找键。

using System;
using System.Collections.Generic;

namespace MemoryEfficientFoo
{
    class Foo // This is our data structure 
    {
        public int A;
        public string B;
        public Decimal C;
    }

    /// <summary>
    /// List which does store Foos with much less memory if many values are equal. You can cut memory consumption by factor 3 or if all values 
    /// are different you consume 5 times as much memory as if you would store them in a plain list! So beware that this trick
    /// might not help in your case. Only if many values are repeated it will save memory.
    /// </summary>
    class FooList : IEnumerable<Foo> 
    {
        Dictionary<int, string> Index2B = new Dictionary<int, string>();
        Dictionary<string, int> B2Index = new Dictionary<string, int>();

        Dictionary<int, Decimal> Index2C = new Dictionary<int, decimal>();
        Dictionary<Decimal,int> C2Index = new Dictionary<decimal,int>();

        struct FooIndex
        {
            public int A;
            public int BIndex;
            public int CIndex;
        }

        // List of foos which do contain only the index values to the dictionaries to lookup the data later.
        List<FooIndex> FooValues = new List<FooIndex>();

        public void Add(Foo foo)
        {
            int bIndex;
            if(!B2Index.TryGetValue(foo.B, out bIndex))
            {
                bIndex = B2Index.Count;
                B2Index[foo.B] = bIndex;
                Index2B[bIndex] = foo.B;
            }

            int cIndex;
            if (!C2Index.TryGetValue(foo.C, out cIndex))
            {
                cIndex = C2Index.Count;
                C2Index[foo.C] = cIndex;
                Index2C[cIndex] = cIndex;
            }

            FooIndex idx = new FooIndex
            {
                A = foo.A,
                BIndex = bIndex,
                CIndex = cIndex
            };

            FooValues.Add(idx);
        }

        public Foo GetAt(int pos)
        {
            var idx = FooValues[pos];
            return new Foo
            {
                A = idx.A,
                B = Index2B[idx.BIndex],
                C = Index2C[idx.CIndex]
            };
        }

        public IEnumerator<Foo> GetEnumerator()
        {
            for (int i = 0; i < FooValues.Count; i++)
            {
                yield return GetAt(i);
            }
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            FooList list = new FooList();
            List<Foo> fooList = new List<Foo>();
            long before = GC.GetTotalMemory(true);
            for (int i = 0; i < 1000 * 1000; i++)
            {
                list
                //fooList
                    .Add(new Foo
                    {
                        A = i,
                        B = "Hi",
                        C = i
                    });

            }
            long after = GC.GetTotalMemory(true);
            Console.WriteLine("Did consume {0:N0}bytes", after - before);
        }
    }
}

可以找到类似的内存保存列表here