短字符串(标签名称)的最佳 32 位哈希函数是啥?
Posted
技术标签:
【中文标题】短字符串(标签名称)的最佳 32 位哈希函数是啥?【英文标题】:What is the best 32bit hash function for short strings (tag names)?短字符串(标签名称)的最佳 32 位哈希函数是什么? 【发布时间】:2011-01-22 00:09:12 【问题描述】:对于较短的字符串,最好的 32 位散列函数是什么?
字符串是由英文字母、数字、空格和一些附加字符(#
、$
、.
、...)组成的标签名称。例如:Unit testing
、C# 2.0
。
我正在寻找“最佳”,就像“最小碰撞”一样,性能对我的目标并不重要。
【问题讨论】:
可能重复***.com/questions/251346/… 不完全如此,因为我的问题在哈希大小方面更具体,而忽略了性能。此外,我不只是在寻找 a 哈希函数,而是在寻找有意义的选择——我知道有 CRC32 和 FNV32,但哪个更适合我的域? 你的标签列表是固定在一组字符串上还是会随着时间动态增长? 标签是由人添加的,所以我无法预测它们(但有长度和字符限制)。 以下页面有几种通用哈希函数的实现,它们高效且冲突最小:partow.net/programming/hashfunctions/index.html 【参考方案1】:使用MaPrime2c
哈希函数:
static const unsigned char sTable[256] =
0xa3,0xd7,0x09,0x83,0xf8,0x48,0xf6,0xf4,0xb3,0x21,0x15,0x78,0x99,0xb1,0xaf,0xf9,
0xe7,0x2d,0x4d,0x8a,0xce,0x4c,0xca,0x2e,0x52,0x95,0xd9,0x1e,0x4e,0x38,0x44,0x28,
0x0a,0xdf,0x02,0xa0,0x17,0xf1,0x60,0x68,0x12,0xb7,0x7a,0xc3,0xe9,0xfa,0x3d,0x53,
0x96,0x84,0x6b,0xba,0xf2,0x63,0x9a,0x19,0x7c,0xae,0xe5,0xf5,0xf7,0x16,0x6a,0xa2,
0x39,0xb6,0x7b,0x0f,0xc1,0x93,0x81,0x1b,0xee,0xb4,0x1a,0xea,0xd0,0x91,0x2f,0xb8,
0x55,0xb9,0xda,0x85,0x3f,0x41,0xbf,0xe0,0x5a,0x58,0x80,0x5f,0x66,0x0b,0xd8,0x90,
0x35,0xd5,0xc0,0xa7,0x33,0x06,0x65,0x69,0x45,0x00,0x94,0x56,0x6d,0x98,0x9b,0x76,
0x97,0xfc,0xb2,0xc2,0xb0,0xfe,0xdb,0x20,0xe1,0xeb,0xd6,0xe4,0xdd,0x47,0x4a,0x1d,
0x42,0xed,0x9e,0x6e,0x49,0x3c,0xcd,0x43,0x27,0xd2,0x07,0xd4,0xde,0xc7,0x67,0x18,
0x89,0xcb,0x30,0x1f,0x8d,0xc6,0x8f,0xaa,0xc8,0x74,0xdc,0xc9,0x5d,0x5c,0x31,0xa4,
0x70,0x88,0x61,0x2c,0x9f,0x0d,0x2b,0x87,0x50,0x82,0x54,0x64,0x26,0x7d,0x03,0x40,
0x34,0x4b,0x1c,0x73,0xd1,0xc4,0xfd,0x3b,0xcc,0xfb,0x7f,0xab,0xe6,0x3e,0x5b,0xa5,
0xad,0x04,0x23,0x9c,0x14,0x51,0x22,0xf0,0x29,0x79,0x71,0x7e,0xff,0x8c,0x0e,0xe2,
0x0c,0xef,0xbc,0x72,0x75,0x6f,0x37,0xa1,0xec,0xd3,0x8e,0x62,0x8b,0x86,0x10,0xe8,
0x08,0x77,0x11,0xbe,0x92,0x4f,0x24,0xc5,0x32,0x36,0x9d,0xcf,0xf3,0xa6,0xbb,0xac,
0x5e,0x6c,0xa9,0x13,0x57,0x25,0xb5,0xe3,0xbd,0xa8,0x3a,0x01,0x05,0x59,0x2a,0x46
;
#define PRIME_MULT 1717
unsigned int
maPrime2cHash (unsigned char *str, unsigned int len)
unsigned int hash = len, i;
for (i = 0; i != len; i++, str++)
hash ^= sTable[( *str + i) & 255];
hash = hash * PRIME_MULT;
return hash;
查看 www.amsoftware.narod.ru/algo2.html 以了解 MaFastPrime、MaRushPrime 等测试。
【讨论】:
【参考方案2】:我不确定这是否是最佳选择,但这里有一个字符串的哈希函数:
The Practice of Programming(哈希表,第 57 页)
/* hash: compute hash value of string */
unsigned int hash(char *str)
unsigned int h;
unsigned char *p;
h = 0;
for (p = (unsigned char*)str; *p != '\0'; p++)
h = MULTIPLIER * h + *p;
return h; // or, h % ARRAY_SIZE;
根据经验,值 31 和 37 已被证明是 ASCII 字符串散列函数中乘数的不错选择。
【讨论】:
是的,我们使用这个精确的散列函数,MULTIPLIER = 37 用于字符串和路径。对我们来说效果很好,即使在 2 年后我还没有遇到过碰撞问题(当然不能保证我们不会) 这看起来确实很简单。如果更简单的方法有效,为什么会创建 FNV? @Andrey Shchekin,我在处理原始字节(blob)时使用 FNV 哈希。也许,上面的函数特别是使用字符串会产生更好的结果。我不确定。 我注意到 Perl 的哈希算法使用了 MULTIPLIER=33,并在最后做了一个额外的步骤:h += (h >> 5) 来改善低阶位的分布。 此算法是cse.yorku.ca/~oz/hash.html 讨论的变体之一。不幸的是,它很容易受到基本的哈希冲突攻击(参见 [ocert.org/advisories/ocert-2011-003.html]),因为使用基于子字符串(参见参考论文)的冲突计算是微不足道的;但如果从不与外部提供的密钥一起使用,它可能会很好地工作。【参考方案3】:我很抱歉这么晚才回复。今年早些时候,我撰写了一个标题为Hashing Short Strings 的页面,这可能对这次讨论有所帮助。总之,我发现 CRC-32 和 FNV-1a 在散列短字符串方面更胜一筹。在我的测试中,它们是高效的,并且产生了广泛分布且无冲突的哈希值。我惊讶地发现,当输出折叠到 32 位时,MD5、SHA-1 和 SHA-3 会产生少量冲突。
【讨论】:
CRC32 仍然是这里的最佳答案 我也觉得CRC32应该是排名靠前的答案 其实,CRC32 distribution is quite terrible compared to alternatives。对于 32 位散列,即使是像 product/rotation 这样的简单算法也可以为 xxHash 正是这样做的,但具有更好的分布,并且专门针对现代处理器进行了优化(与 CRC32 非常不同)。为了散列大量具有较少冲突的小字符串(例如在词法分析时),DJB2 可能是最佳选择。 @yyny 上面发布的文章显示了 DJB2 算法对 2 个字符长的字符串产生 2220 次冲突,对 3 个字符长产生 70164 次冲突。碰撞率低得多的散列(例如 FNV-1a)是否更适合散列大量小字符串?【参考方案4】:这取决于您的硬件。
在现代硬件上,即带有 SSE4.2 或 arm7 的 Intel/AMD,您应该使用内部 _mm_crc32_uxx
内在函数,因为它们最适合短字符串。 (也适用于长键,但最好使用 Adler 的线程版本,如 zlib)
在旧的或未知的硬件上,要么运行时探测 SSE4.2 或 CRC32 功能,要么只使用一个简单的好哈希函数。例如。 Murmur2 或城市
质量和性能概述如下: https://github.com/rurban/smhasher#smhasher
还有所有的实现。最喜欢的是https://github.com/rurban/smhasher/blob/master/crc32_hw.c 和https://github.com/rurban/smhasher/blob/master/MurmurHash2.cpp
如果您事先知道密钥,请使用完美哈希,而不是哈希函数。例如。 gperf 或我的 phash:https://github.com/rurban/Perfect-Hash#name
如今,通过 c 编译器生成完美的哈希非常快,您甚至可以即时创建它们并动态加载。
【讨论】:
更新:Murmur2 和 City 不能再被称为简单的好哈希函数了。最快的是 FNV1 或 CRC32-C,更好的是 Metro 或 Farmhash。 SpookyHash64 在我发现的所有哈希函数中仍然具有最好的雪崩/最低碰撞率,我强烈建议将它用于罗宾汉哈希图,除非您凭经验发现其他哈希函数更好/快点。对于小型输入,我会推荐 FNV1A 或 DJB2。 SpookyHash 在大约 30 个周期内具有相当高的设置成本。 Metro/Farm/Murmur/City/xxHash/许多其他的非常适合快速、通用的散列,设置时间较短,但冲突率较高。当低碰撞率很重要时,我不会使用它们。【参考方案5】:如果您的程序需要与其他系统通信,最好使用众所周知的算法。快速而肮脏的方法是使用 md5 哈希的前几个字符。您不需要花费数小时或数天在您的项目中发明***。
缺点是发生碰撞的机会很高。但是,如果您的哈希用于带时间戳的会话或短生命周期任务。使用它没有问题。
【讨论】:
【参考方案6】:如果用户添加新标签的情况很少见,那么您可以使用每次添加新标签时都会重新计算的完美哈希 (http://en.wikipedia.org/wiki/Perfect_hash_function)。当然,在不知道您真正想要解决的问题的情况下,猜测您可能会做什么只是猜测。
【讨论】:
【参考方案7】:如果性能不重要,只需采用 MD5 或 SHA1 等安全哈希,并将其输出截断为 32 位。这将为您提供与随机无法区分的哈希码分布。
【讨论】:
md5 非常适合这种情况 MD4(参见tools.ietf.org/html/rfc1320)可能会更好,因为它比 MD5 实现起来稍微简单一些。请注意,MD4 和 MD5 都与随机无法区分(两者都被“密码破解”),但它们仍然足够接近手头的目的。 你认为它的碰撞会比尼克 D 的答案少吗?我有点犹豫要批准/使用什么。 @Thomas MD5 在您可以创建哈希冲突的意义上被破坏 - 两个明文产生相同的哈希。这并不意味着 MD5 的输出可以与随机性区分开来——没有针对 MD5 的原像攻击。哪个更容易实现也无关紧要 - 他几乎肯定会用他选择的语言预先制作 MD5 或 SHA1 实现。 @Nick:对 MD5 的攻击基于差分路径。通过在 MD5 输入上应用输入差异,您在输出中找到预期差异的概率很小但高于随机概率。这不会导致原像攻击,但它使 MD5 与随机预言机区分开来。在 MD4 的情况下,当在 HMAC 中使用时(在学术上),这被证明是可利用的(无需担心冲突本身)。【参考方案8】:您可以查看 murmurhash2。它速度很快,也适用于小弦乐,并且具有良好的混合最后一步,因此对于非常小的弦乐也能很好地混合。
【讨论】:
以上是关于短字符串(标签名称)的最佳 32 位哈希函数是啥?的主要内容,如果未能解决你的问题,请参考以下文章