【问题标题】:Generate a Hashcode for a string that is platform independent为与平台无关的字符串生成哈希码
【发布时间】:2012-02-08 17:57:16
【问题描述】:

我们有一个应用程序

  • 在字符串上生成哈希码
  • 将该哈希码与相关数据一起保存到数据库中
  • 稍后,它使用字符串哈希码查询数据库以检索数据

这显然是一个错误,因为从 string.GetHashCode() 返回的值因 .NET 版本和体系结构(32/64 位)而异。更复杂的是,我们离发布太近了,无法重构我们的应用程序以停止序列化哈希码并只查询字符串。我们现在想做的是想出一个快速而肮脏的修复,然后重构代码以正确的方式完成。

快速而肮脏的修复似乎是创建一个跨架构一致的静态 GetInvariantHashCode(string s) 辅助方法。

能否建议一种算法,用于在字符串上生成与 32 位和 64 位架构等效的哈希码?

还有一些注意事项:

  • 我知道 HashCode 不是唯一的。如果哈希码在两个不同的字符串上返回匹配项,我们会对结果进行后处理以找到精确匹配项。它不用作主键。
  • 我相信架构师的意图是通过查询 long 而不是 NVarChar 来加快搜索速度

【问题讨论】:

  • 正如其他人指出的那样,您想要做的事情根本不可能。我没有看到可以解决您的问题并避免碰撞的方法。
  • 好吧,如果这是你的意图,让数据库为你索引字符串。

标签: c# .net hash hashcode


【解决方案1】:

我知道 HashCodes 不是唯一的。如果哈希码在两个不同的字符串上返回匹配项,我们会对结果进行后处理以找到精确匹配项。它不用作主键。

我相信架构师的意图是通过查询 long 而不是 NVarChar 来加快搜索速度

那就让数据库为你索引字符串吧!

听着,我不知道您的域有多大,但是如果它的大小足够大,那么您将很快发生冲突,而且很有可能发生冲突。相对于生日的数量,这是很多人的生日问题。您将发生碰撞,并且失去任何您可能认为不仅通过首先索引字符串而获得的速度增益。

无论如何,如果您距离发布还有几天的时间,您就不需要我们,而且您确实需要跨平台的不变哈希码。您可以使用非常愚蠢、非常快速的哈希码实现。见鬼,你可以在眨眼之间自己想出一个:

string s = "Hello, world!";
int hash = 17;
foreach(char c in s) {
    unchecked { hash = hash * 23 + c.GetHashCode(); } 
}

或者您可以使用旧的 Bernstein 哈希。不断地。他们会给你带来你正在寻找的性能提升吗?我不知道,它们不应该用于此目的。它们旨在用于平衡哈希表。您没有平衡哈希表。你使用了错误的概念。

编辑(以下是在使用新的显着信息编辑问题之前编写的)

理论上,如果不对输入空间进行某种限制,您根本无法做到这一点。您的问题比String.GetHashCode 严重得多,因平台而异。

string 的实例很多。事实上,比Int32 的实例多得多。所以,由于 piegonhole 原则,你会发生碰撞。您无法避免这一点:您的strings 是鸽子,而您的Int32 哈希码是鸽笼,鸽笼中有太多鸽子可以进入鸽笼,而某些鸽笼不会超过一只鸽子。由于冲突问题,您不能使用哈希码作为字符串的唯一键。它不起作用。期间。

使当前提议的设计工作(使用Int32 作为string 实例的标识符)的唯一方法是将字符串的输入空间限制为大小小于或等于Int32s 的数量。即使这样,您也很难想出一种算法,以一种独特的方式将您的输入空间 strings 映射到 Int32

即使您尝试使用 SHA-512 或其他方式增加鸽巢的数量,您仍然有可能发生冲突。我怀疑您之前在设计中考虑过这种可能性;此设计路径是 DOA。无论如何,这不是 SHA-512 的用途,它不能用于消息的唯一标识。这只是为了减少消息伪造的可能性。

更复杂的是,我们离发布太近了,无法重构我们的应用程序以停止序列化哈希码并只查询字符串。

好吧,那么你有大量的工作要做。很抱歉你在游戏中这么晚才发现这一点。

我注意到String.GetHashCode 的文档:

GetHashCode 的行为取决于它的实现,它可能会从一个版本的公共语言运行时更改为另一个版本。发生这种情况的一个原因是为了提高GetHashCode 的性能。

来自Object.GetHashCode

GetHashCode 方法适用于哈希算法和哈希表等数据结构。

哈希码用于平衡哈希表。它们不是用来识别物体的。如果您将这个概念用于它的用途,您可能会更早发现这一点。

【讨论】:

  • 说真的,我不知道你刚刚发现这个的店里发生了什么。
  • 有人试图做一些聪明的事;事实证明,聪明!=正确。令人震惊的是,这通过了设计审查、代码审查并通过了 SQA。然而,一个(公认的重大)设计缺陷并不是软件团队不称职的明确证据。
  • 对不起,我认为这是一个重大的失败。
  • 显然,正确性比性能更重要。我认为自定义散列或 Bernstein 散列听起来像是快速而肮脏的解决方法。
  • 至少有人(我)在客户报告问题之前注意到了问题。 :-D
【解决方案2】:

你应该只使用 SHA512。

请注意,哈希不是(也不能是)唯一的。
如果您需要它是唯一的,只需使用标识函数作为您的哈希。

【讨论】:

  • 您甚至可以只使用 SHA256。您不必担心哈希码不是唯一的。即使是那些竭尽全力寻找 SHA256 冲突的人也无法做到。与意外发现 SHA256 碰撞相比,您更有可能在同一个月内被闪电击中五次。
  • @DavidSchwartz:您仍然不想依赖明显错误的假设。 (即使你可能不会有超过 2^256 个字符串)
  • 你必须是个十足的傻瓜才能在窗户打开时在你的前门上加第十五把锁。有些故障模式的可能性比 SHA256 冲突高出数十亿倍。你不妨争辩说,不让你的屋顶小行星证明是“依赖于错误的假设”,即你的房子不会被小行星撞击。
  • @David Schwartz:我认为你有点误解了人们没有发现碰撞的含义。这意味着如果有人给你一个 SHA-256 哈希,你能找到两个字符串 st 都产生这个哈希,你能不使用蛮力就做到吗?现在,我们不能。然而,这并不意味着不会发生碰撞。从理论上讲,找到碰撞很容易。只需制作 2^256 + 1 个字符串并将它们全部散列,然后我们在那一堆散列中的某个地方发生冲突。
  • @Jason:虽然这显然是正确的,但 no-one 处理 2^256 个不同的字符串。这比宇宙中原子的数量还要多。大卫可能是对的;在一般使用中,您可能不需要担心碰撞(我没有这种信念的来源,但听起来是正确的)。但是,我喜欢我的设计具有建筑风格。
【解决方案3】:

您可以使用托管加密类之一(例如SHA512Managed)通过ComputeHash 计算与平台无关的哈希。这将需要将字符串转换为字节数组(即:使用Encoding.GetBytes 或其他方法),速度较慢,但​​要保持一致。

话虽如此,散列并不能保证唯一,并且实际上不是数据库中唯一性的适当机制。使用哈希存储数据很可能会导致数据丢失,因为第一次哈希冲突会覆盖旧数据(或丢弃新数据)。

【讨论】:

  • ...除非数据库设计允许哈希冲突。例如,存储哈希值的列可能具有非唯一索引。
  • @phoog True - 但这就是为什么我建议这不是检查“数据库中的唯一性”的好方法;)
  • 糟糕!由于阅读太快,我不知何故错过了句子的那一部分。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-07
  • 2012-08-23
  • 2015-12-06
  • 1970-01-01
  • 2016-02-06
相关资源
最近更新 更多