具有 32 位整数的低冲突率的快速字符串散列算法 [关闭]
Posted
技术标签:
【中文标题】具有 32 位整数的低冲突率的快速字符串散列算法 [关闭]【英文标题】:Fast String Hashing Algorithm with low collision rates with 32 bit integer [closed] 【发布时间】:2010-09-11 23:04:01 【问题描述】:我有很多不相关的命名事物,我想对其进行快速搜索。 “土豚”总是到处都是“土豚”,因此散列字符串并重用整数可以很好地加快比较速度。整个名称集是未知的(并且随着时间的推移而变化)。什么是生成小(32 或 16)位值且冲突率低的快速字符串散列算法?
我希望看到特定于 C/C++ 的优化实现。
【问题讨论】:
请添加关键字:哈希算法唯一低冲突 以下页面有几种通用哈希函数的实现,它们是“高性能”且“冲突率”低:partow.net/programming/hashfunctions/index.html 【参考方案1】:Murmur Hash 很不错。
【讨论】:
是的,这是当前领先的哈希表通用哈希函数。当然,它是非加密的,有一对明显的差异。 注意:MurmurHash3 的新 URL 是 code.google.com/p/smhasher【参考方案2】:FNV variants 之一应该满足您的要求。它们速度很快,并且产生相当均匀分布的输出。
【讨论】:
如果您要使用 FNV,请坚持使用 FNV-1a,因为它在雪崩测试中具有可接受的结果(请参阅 home.comcast.net/~bretm/hash/6.html)。或者只使用 MurmurHash2,它在速度和分布上都更好 (murmurhash.googlepages.com)。 @Steven:MurmurHash 哈希仅由其作者分析过。我在几个不同的场景中使用过它,新版本的 FNV 似乎做得更好。 @sonicoder:虽然我不会过度推销 MurmurHash,但普通的 FNV 非常糟糕,而 FNV-1a 只能通过。碰巧的是,MurmurHash 已被广泛分析并发现有用。它仍然不是加密哈希,无论如何都会发生冲突,但与任何类型的 FNV 相比,它仍然是一个巨大的改进。 @Steven Sudit:正如我所说,它“仅”由其作者分析,没有其他人。因此,“分析”的结果并不真正客观。 @sonicoder:我会更直白地说:不,你错了。包括学术机构在内的许多第三方对其进行了分析。访问 Wikipedia 获取链接。更重要的是,它不仅总体上做得很好,而且通过创建 MurmurHash3 解决了所发现的具体缺陷。【参考方案3】:对于固定的字符串集,使用 gperf。
如果您的字符串集发生变化,您必须选择一个哈希函数。该主题之前已讨论过:
What's the best hashing algorithm to use on a stl string when using hash_map?
【讨论】:
完美的哈希是一个非常优雅的解决方案,如果可用的话。【参考方案4】:还有一个nice articleeternallyconfuzzled.com。
Jenkins 的字符串一次性哈希应该如下所示:
#include <stdint.h>
uint32_t hash_string(const char * s)
uint32_t hash = 0;
for(; *s; ++s)
hash += *s;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
【讨论】:
【参考方案5】:根据您的用例可能更好的另一种解决方案是 interned strings。这就是符号的工作方式,例如在 Lisp 中。
实习字符串是一个字符串对象,其值为实际字符串字节的地址。因此,您通过签入全局表来创建一个内部字符串对象:如果该字符串在其中,则将内部字符串初始化为该字符串的地址。如果没有,则插入它,然后初始化您的实习字符串。
这意味着从同一个字符串构建的两个内部字符串将具有相同的值,即一个地址。因此,如果 N 是您系统中的留存字符串数,则其特征是:
构建缓慢(需要查找和可能的内存分配) 在并发线程的情况下需要全局数据和同步 比较是 O(1),因为您比较的是地址,而不是实际的字符串字节(这意味着排序效果很好,但不会是字母排序)。干杯,
卡尔
【讨论】:
【参考方案6】:一个好的主题永远不会迟到,我相信人们会对我的发现感兴趣。
我需要一个哈希函数,在阅读了这篇文章并对此处给出的链接进行了一些研究之后,我想出了 Daniel J Bernstein 算法的这种变体,我曾经用它进行了一个有趣的测试:
unsigned long djb_hashl(const char *clave)
unsigned long c,i,h;
for(i=h=0;clave[i];i++)
c = toupper(clave[i]);
h = ((h << 5) + h) ^ c;
return h;
这种变体忽略大小写对字符串进行哈希处理,这适合我对用户登录凭据进行哈希处理的需要。 “clave”在西班牙语中是“key”。我为西班牙语感到抱歉,但它是我的母语,程序是写在上面的。
好吧,我编写了一个程序,它将生成从“test_aaaa”到“test_zzzz”的用户名,并且-为了使字符串更长-我在这个列表中添加了一个随机域:“cloud-nueve.com”,“ yahoo.com”、“gmail.com”和“hotmail.com”。因此,他们每个人看起来像:
test_aaaa@cloud-nueve.com, test_aaab@yahoo.com, test_aaac@gmail.com、test_aaad@hotmail.com 等等。这里是测试的输出 -'Colision entre XXX y XXX' 意思是 'XXX 和 XXX 的碰撞'。 “palabras”的意思是“单词”,而“Total”在两种语言中都是一样的——。
布斯坎多·科利西内斯... 碰撞进入 'test_phiz@hotmail.com' y 'test_juxg@cloud-nueve.com' (1DB903B7) 冲突进入 'test_rfhh@hotmail.com' y 'test_fpgo@yahoo.com' (2F5BC088) 碰撞进入 'test_wxuj@hotmail.com' y 'test_pugy@cloud-nueve.com' (51FD09CC) 碰撞进入 'test_sctb@gmail.com' y 'test_iohw@cloud-nueve.com' (52F5480E) 碰撞进入 'test_wpgu@cloud-nueve.com' y 'test_seik@yahoo.com' (74FF72E2) 冲突进入 'test_rfll@hotmail.com' y 'test_btgo@yahoo.com' (7FD70008) 碰撞进入 'test_wcho@cloud-nueve.com' y 'test_scfz@gmail.com' (9BD351C4) 碰撞进入 'test_swky@cloud-nueve.com' y 'test_fqpn@gmail.com' (A86953E1) 冲突进入 'test_rftd@hotmail.com' y 'test_jlgo@yahoo.com' (BA6B0718) 冲突进入 'test_rfpp@hotmail.com' y 'test_nxgo@yahoo.com' (D0523F88) 碰撞进入 'test_zlgo@yahoo.com' y 'test_rfdd@hotmail.com' (DEE08108) 总de Colisones:11 总 de Palabras : 456976这还不错,456,976 次中有 11 次冲突(当然使用完整的 32 位作为表长度)。
使用 5 个字符运行程序,即从 'test_aaaaa' 到 'test_zzzzz',实际上会耗尽构建表的内存。下面是输出。 'No hay memoria para insertar XXXX (insertadas XXX)' 的意思是'没有内存可以插入 XXX (XXX 插入)'。基本上 malloc() 在那一点上失败了。
没有干草记忆 para insertar 'test_epjcv' (insertadas 2097701)。 布斯坎多·科利西内斯... ...451 个“碰撞”字符串... 总de Colisones:451 总 de Palabras : 2097701这意味着 2,097,701 个字符串上只有 451 次冲突。请注意,在任何情况下,每个代码都没有超过 2 次冲突。我确认这对我来说是一个很好的哈希,因为我需要将登录 ID 转换为 40 位唯一 ID 以进行索引。所以我使用它来将登录凭据转换为 32 位哈希,并使用额外的 8 位来处理每个代码最多 255 次冲突,这在测试结果中几乎是不可能生成的。
希望这对某人有用。
编辑:
就像测试盒是AIX一样,我使用LDR_CNTRL=MAXDATA=0x20000000来运行它,给它更多的内存,它运行的时间更长,结果在这里:
Buscando Colisones... 总de Colisones:2908 总 de Palabras : 5366384
这是 5,366,384 次尝试后的 2908!
非常重要:使用 -maix64 编译程序(所以 unsigned long 是 64 位),所有情况下的冲突次数都是 0!!!
【讨论】:
【参考方案7】:Hsieh 散列函数非常好,并且有一些基准/比较,作为 C 中的一般散列函数。根据您想要的(不是很明显),您可能需要考虑类似 cdb 的东西.
【讨论】:
【参考方案8】:你为什么不直接使用Boost libraries? 他们的散列函数很容易使用,而且Boost 中的大部分东西很快就会成为C++ 标准的一部分。其中一些已经是。
Boost hash 就这么简单
#include <boost/functional/hash.hpp>
int main()
boost::hash<std::string> string_hash;
std::size_t h = string_hash("Hash me");
你可以在boost.org找到提升
【讨论】:
STL 和 boost tr1 对字符串的哈希函数都很弱。【参考方案9】:Bob Jenkins has many hash functions available,所有这些都速度快且碰撞率低。
【讨论】:
哈希值非常可靠,技术上很有趣,但不一定很快。考虑一次一个哈希处理一个字节一个字节的输入,而其他哈希一次需要 4 甚至 8 个字节。速度差异很大! Bob 的哈希值非常快:azillionmonkeys.com/qed/hash.html【参考方案10】:看看 GNU gperf。
【讨论】:
完美的哈希生成器赞! 完美散列不适用于此应用程序,因为名称集是未知的并且会发生变化。因此,gperf 不适用于此。【参考方案11】:您可以使用 Reflector 来查看 .NET 在 String.GetHashCode() 方法上的使用情况。
我会大胆猜测微软花了相当多的时间来优化这个。他们也打印在所有 MSDN 文档中,它随时可能更改。很明显,这是在他们的“性能调整雷达”上;-)
我想移植到 C++ 也很简单。
【讨论】:
【参考方案12】:这个previous question有一些很好的讨论
还有一个很好的概述如何选择哈希函数,以及几个常见的分布统计here
【讨论】:
【参考方案13】:这里描述的是一种自己实现的简单方法:http://www.devcodenote.com/2015/04/collision-free-string-hashing.html
帖子中的一个sn-p:
如果说我们有一个大写英文字母的字符集,那么字符集的长度是 26,其中 A 可以用数字 0 表示,B 可以用数字 1 表示,C 可以用数字 2 表示,依此类推直到 Z由数字 25。现在,每当我们想将此字符集的字符串映射到唯一数字时,我们执行与二进制格式相同的转换
【讨论】:
如果给定从 24 到 1.111.998 个代码点的字符集,那(给定一个显示链接内容的超文本浏览器)如何映射到(32 or 16) bit values
?基本转换不是有用的哈希函数。【参考方案14】:
CRC-32。谷歌上有大约一万亿个链接。
【讨论】:
CRC 专为错误检测和纠正而设计。它们的分布特征通常不是很好。 Arachnid 显然从未尝试过将 CRC32 作为哈希值。他们运作良好。 "CRC32 从未打算用于哈希表。确实没有充分的理由将其用于此目的。"参看。 home.comcast.net/~bretm/hash/8.html以上是关于具有 32 位整数的低冲突率的快速字符串散列算法 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章