我发现VS2005上的标准哈希函数在尝试实现高性能查找时非常缓慢。快速有效的哈希算法有哪些优秀的例子可以解决大多数冲突?
答案 0 :(得分:63)
我与Microsoft Research的{{3}}合作进行了一些散列表实现。他研究了各种数据集上的一些字符串散列函数,发现简单地乘以101和add循环的效果非常好。
unsigned int
hash(
const char* s,
unsigned int seed = 0)
{
unsigned int hash = seed;
while (*s)
{
hash = hash * 101 + *s++;
}
return hash;
}
答案 1 :(得分:19)
从我的一些旧代码:
/* magic numbers from http://www.isthe.com/chongo/tech/comp/fnv/ */
static const size_t InitialFNV = 2166136261U;
static const size_t FNVMultiple = 16777619;
/* Fowler / Noll / Vo (FNV) Hash */
size_t myhash(const string &s)
{
size_t hash = InitialFNV;
for(size_t i = 0; i < s.length(); i++)
{
hash = hash ^ (s[i]); /* xor the low 8 bits */
hash = hash * FNVMultiple; /* multiply by the magic number */
}
return hash;
}
它很快。真的吓坏了。
答案 2 :(得分:8)
Boost有一个boost::hash库,可以为大多数常见类型提供一些基本的哈希函数。
答案 3 :(得分:7)
这总取决于您的数据集。
我通过使用字符串的CRC32获得了令人惊讶的好结果。适用于各种不同的输入集,效果非常好。
网上很容易找到很多好的CRC32实现。
编辑:几乎忘了:此页面有一个很好的哈希函数枪战,包含性能数据和测试数据:
http://smallcode.weblogs.us/&lt; - 页面下方。
答案 4 :(得分:6)
我使用Jenkins哈希来编写Bloom过滤器库,它具有很好的性能。
详细信息和代码可在此处获取:http://burtleburtle.net/bob/c/lookup3.c
这是Perl用于散列操作的函数,fwiw。
答案 5 :(得分:6)
如果要散列一组固定的单词,最好的散列函数通常是perfect hash function。但是,它们通常要求在编译时知道您尝试散列的单词集。检测lexer中的关键字(以及将关键字转换为令牌)是使用gperf等工具生成的完美哈希函数的常见用法。完美哈希还允许您使用简单数组或hash_map
替换vector
。
如果你没有散列一组固定的单词,那么显然这不适用。
答案 6 :(得分:2)
字符串哈希的一个经典建议是逐个逐字逐句地将它们的ascii / unicode值添加到累加器,每次将累加器乘以素数。 (允许哈希值溢出)
template <> struct myhash{};
template <> struct myhash<string>
{
size_t operator()(string &to_hash) const
{
const char * in = to_hash.c_str();
size_t out=0;
while(NULL != *in)
{
out*= 53; //just a prime number
out+= *in;
++in;
}
return out;
}
};
hash_map<string, int, myhash<string> > my_hash_map;
如果没有丢弃数据,很难比这更快。如果您知道您的字符串只能通过几个字符而不是它们的全部内容来区分,那么您可以更快地完成。
如果值太频繁计算,您可以尝试通过创建记住其哈希值的basic_string的新子类来更好地缓存哈希值。但是hash_map应该在内部执行。
答案 7 :(得分:2)
我做了一些搜索,有趣的是,Paul Larson的小算法出现在这里 http://www.strchr.com/hash_functions 因为在许多条件下具有最少的任何测试碰撞,并且它对于展开或桌面驱动的速度非常快。
Larson是简单乘以101并在上面添加循环。
答案 8 :(得分:2)
答案 9 :(得分:1)
来自Hash Functions all the way down:
MurmurHash非常受欢迎,至少在游戏开发者圈子中,作为“一般哈希函数”。
这是一个不错的选择,但稍后我们会看到我们能否做得更好。另一个很好的选择,特别是如果你对你的数据有了更多的了解,而不是“它将是一个未知的字节数”,那就是自己动手(例如看看Won Chun的回复,或Rune修改后的xxHash / Murmur专用于4字节键)等等。)。如果您了解自己的数据,请始终尝试查看该知识是否可用于取得良好效果!
如果没有更多信息,我建议将MurmurHash作为一般用途non-cryptographic hash function。对于小字符串(程序中平均标识符的大小),非常简单且着名的djb2和FNV非常好。
这里(数据大小&lt; 10字节)我们可以看到其他算法的ILP智能性无法显示出来,而FNV或djb2的超级简单性在性能上获胜。
unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
hash = FNV_offset_basis
for each byte_of_data to be hashed
hash = hash × FNV_prime
hash = hash XOR byte_of_data
return hash
hash = FNV_offset_basis
for each byte_of_data to be hashed
hash = hash XOR byte_of_data
hash = hash × FNV_prime
return hash
散列函数可以使您的代码容易受到拒绝服务攻击。如果攻击者能够强制您的服务器处理太多冲突,则您的服务器可能无法处理请求。
某些哈希函数(如MurmurHash)接受一个种子,您可以提供该种子以大幅降低攻击者预测服务器软件生成的哈希值的能力。记住这一点。
答案 10 :(得分:0)
如果您的字符串平均长度超过单个缓存行,但它们的长度+前缀相当独特,请考虑仅使用长度+前8/16个字符。 (长度包含在std :: string对象本身,因此便宜阅读)