如何从C#中的字节数组生成哈希码?
假设我有一个存储字节数组的对象,并且希望能够有效地为其生成哈希码。过去,我已经使用了加密哈希函数,因为它们易于实现,但与单方面进行加密相比,它们所做的工作要多得多,而且我对此并不在乎(我只是在使用哈希码作为哈希表的键)。
这是我今天所拥有的:
struct SomeData : IEquatable<SomeData> { private readonly byte[] data; public SomeData(byte[] data) { if (null == data || data.Length <= 0) { throw new ArgumentException("data"); } this.data = new byte[data.Length]; Array.Copy(data, this.data, data.Length); } public override bool Equals(object obj) { return obj is SomeData && Equals((SomeData)obj); } public bool Equals(SomeData other) { if (other.data.Length != data.Length) { return false; } for (int i = 0; i < data.Length; ++i) { if (data[i] != other.data[i]) { return false; } } return true; } public override int GetHashCode() { return BitConverter.ToInt32(new MD5CryptoServiceProvider().ComputeHash(data), 0); } }
有什么想法吗?
dp:没错,我错过了Equals支票,我已经对其进行了更新。使用字节数组中的现有哈希码将导致引用相等(或者至少将相同的概念转换为哈希码)。
例如:
byte[] b1 = new byte[] { 1 }; byte[] b2 = new byte[] { 1 }; int h1 = b1.GetHashCode(); int h2 = b2.GetHashCode();
使用该代码,尽管两个字节数组在其中具有相同的值,但它们引用的是内存的不同部分,并且将导致(可能)不同的哈希码。我需要具有相同内容的两个字节数组的哈希码相等。
解决方案
回答
如果我们正在寻找性能,我测试了一些哈希键,并且
我推荐鲍勃·詹金(Bob Jenkin)的哈希函数。都快疯了
进行计算,并且将产生与密码术一样少的碰撞
我们到目前为止使用的哈希。
我不知道全部猫,也不知道它是否可以与C链接,但是
这是它在C中的实现。
回答
对象的哈希码不需要唯一。
检查规则为:
- 哈希码是否相等?然后调用完整的(慢速)"等于"方法。
- 哈希码不相等吗?那么这两个项目肯定不相等。
我们想要的只是一个" GetHashCode"算法,该算法将集合大致分为几组,因为" HashTable"或者" Dictionary <>"将不需要使用哈希来优化检索,因此不应该构成密钥。
我们希望数据多长时间?如何随机?如果长度变化很大(例如文件),则只需返回长度即可。如果长度可能相似,则查看变化的字节子集。
" GetHashCode"应该比" Equals"要快得多,但不必唯一。
两个相同的事物绝对不能具有不同的哈希码。两个不同的对象不应具有相同的哈希码,但是可能会发生一些冲突(毕竟,比可能的32位整数有更多的排列)。
回答
使用字节数组字段中的现有哈希码还不够好吗?还要注意,在Equals方法中,我们应该在进行比较之前检查数组的大小是否相同。
回答
生成良好的哈希值说起来容易做起来难。记住,我们基本上是在用m位信息表示n字节数据。数据集越大,m越小,发生冲突的可能性就越大……将两个数据解析为相同的哈希。
我所学到的最简单的哈希是将所有字节异或者。它比大多数复杂的哈希算法和用于小型数据集的中途通用哈希算法简单,快速。确实是哈希算法的冒泡排序。由于简单的实现将使我们剩下8位,因此只有256个散列...并不是那么热。我们可以对块进行XOR运算,而不是单个字节,但随后该算法变得更加复杂。
因此,可以肯定的是,密码算法可能正在做一些我们不需要的事情……但是它们在通用哈希质量上也有了巨大的提高。我们使用的MD5散列有128位,可能有数十亿个散列。我们可能会得到更好的改进的唯一方法是对我们希望通过应用程序的数据进行一些有代表性的采样,然后在其上尝试各种算法,以查看遇到了多少冲突。
因此,在我看到不使用固定哈希算法的某些理由(也许是性能?)之前,我将不得不建议我们坚持使用现有的技术。
回答
我们是否将其与SHA1CryptoServiceProvider.ComputeHash方法进行了比较?它需要一个字节数组并返回SHA1哈希,我相信它已经很好地优化了。我在一个Identicon处理程序中使用了该程序,该程序在负载下表现良好。
回答
RuntimeHelpers.GetHashCode可能会有所帮助:
From Msdn: Serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures such as a hash table.
回答
无论我们是想要完美的哈希函数(每个对象的值都相等)还是一个很好的哈希函数,始终都是性能的折衷,通常需要花费时间来计算好的哈希函数,如果数据集很小,那么我们最好快速功能。最重要的是正确性(正如我们在第二篇文章中所指出的那样),要实现这一目标,我们需要返回数组的Length。根据数据集,可能还可以。如果不是这样(例如,所有数组都一样长),则可以使用一些便宜的方法,例如查看第一个和最后一个值并对它们的值进行XOR,然后添加更多的复杂性(如认为适合数据)。
查看哈希函数如何对数据执行的一种快速方法是将所有数据添加到哈希表中,并计算调用Equals函数的次数,如果这种情况经常发生,则我们需要对该函数进行更多的工作。如果执行此操作,请记住,哈希表的大小在开始时需要设置为大于数据集的大小,否则将重新哈希数据,这将触发重新插入和更多的Equals评估(尽管可能更现实吗?)
对于某些对象(不是这个对象),可以通过ToString()。GetHashCode()生成一个快速的HashCode,它当然不是最佳的,但是它很有用,因为人们倾向于从ToString()返回与对象身份相似的东西,而这恰好GetHashcode在寻找什么
Trivia:我见过的最糟糕的性能是有人错误地从GetHashCode返回了一个常数,尽管很容易通过调试器发现,特别是如果我们在哈希表中进行了大量查找
回答
借用JetBrains软件生成的代码,我决定使用此功能:
public override int GetHashCode() { unchecked { var result = 0; foreach (byte b in _key) result = (result*31) ^ b; return result; } }
仅对字节进行异或者运算的问题在于,返回值的3/4(3个字节)只有2个可能的值(全部打开或者全部关闭)。这会使位散布得更多。
在Equals中设置断点是一个很好的建议。将我的数据的大约200,000个条目添加到Dictionary中,可以看到大约10个Equals调用(或者1 / 20,000)。
回答
不要将加密哈希用于哈希表,这太荒谬了。
在这里...在C#中修改了FNV哈希
http://bretm.home.comcast.net/hash/6.html
public static int ComputeHash(params byte[] data) { unchecked { const int p = 16777619; int hash = (int)2166136261; for (int i = 0; i < data.Length; i++) hash = (hash ^ data[i]) * p; hash += hash << 13; hash ^= hash >> 7; hash += hash << 3; hash ^= hash >> 17; hash += hash << 5; return hash; } }