[转]经典字符串哈希算法
| 1 概述 |
| 链表查找的时间效率为O(N),二分法为log2N,B+ Tree为log2N,但Hash链表查找的时间效率为O(1)。 |
| 设计高效算法往往需要使用Hash链表,常数级的查找速度是任何别的算法无法比拟的,Hash链表的构造和冲突的不同实现方法对效率当然有一定的影响,然而Hash函数是Hash链表最核心的部分,本文尝试分析一些经典软件中使用到的字符串Hash函数在执行效率、离散性、空间利用率等方面的性能问题。 |
| 2 经典字符串Hash函数介绍 |
| 作者阅读过大量经典软件原代码,下面分别介绍几个经典软件中出现的字符串Hash函数。 |
| 2.1 PHP中出现的字符串Hash函数 |
| static unsigned long hashpjw(char *arKey, unsigned int nKeyLength) |
| { |
| unsigned long h = 0, g; |
| char *arEnd=arKey+nKeyLength; |
| while (arKey < arEnd) { |
| h = (h << 4) + *arKey++; |
| if ((g = (h & 0xF0000000))) { |
| h = h ^ (g >> 24); |
| h = h ^ g; |
| } |
| } |
| return h; |
| } |
| 2.2 OpenSSL中出现的字符串Hash函数 |
| unsigned long lh_strhash(char *str) |
| { |
| int i,l; |
| unsigned long ret=0; |
| unsigned short *s; |
| if (str == NULL) return(0); |
| l=(strlen(str)+1)/2; |
| s=(unsigned short *)str; |
| for (i=0; i |
| ret^=(s[i]<<(i&0x0f)); |
| return(ret); |
| } */ |
| unsigned long lh_strhash(const char *c) |
| { |
| unsigned long ret=0; |
| long n; |
| unsigned long v; |
| int r; |
| if ((c == NULL) || (*c == '\0')) |
| return(ret); |
| n=0x100; |
| while (*c) |
| { |
| v=n|(*c); |
| n+=0x100; |
| r= (int)((v>>2)^v)&0x0f; |
| ret=(ret<>(32-r)); |
| ret&=0xFFFFFFFFL; |
| ret^=v*v; |
| c++; |
| } |
| return((ret>>16)^ret); |
| } |
| 在下面的测量过程中我们分别将上面的两个函数标记为OpenSSL_Hash1和OpenSSL_Hash2,至于上面的实现中使用MD5算法的实现函数我们不作测试。 |
| 2.3 MySql中出现的字符串Hash函数 |
| #ifndef NEW_HASH_FUNCTION |
| static uint calc_hashnr(const byte *key,uint length) |
| { |
| register uint nr=1, nr2=4; |
| while (length--) |
| { |
| nr^= (((nr & 63)+nr2)*((uint) (uchar) *key++))+ (nr << 8); |
| nr2+=3; |
| } |
| return((uint) nr); |
| } |
| static uint calc_hashnr_caseup(const byte *key,uint length) |
| { |
| register uint nr=1, nr2=4; |
| while (length--) |
| { |
| nr^= (((nr & 63)+nr2)*((uint) (uchar) toupper(*key++)))+ (nr << 8); |
| nr2+=3; |
| } |
| return((uint) nr); |
| } |
| #else |
| uint calc_hashnr(const byte *key, uint len) |
| { |
| const byte *end=key+len; |
| uint hash; |
| for (hash = 0; key < end; key++) |
| { |
| hash *= 16777619; |
| hash ^= (uint) *(uchar*) key; |
| } |
| return (hash); |
| } |
| uint calc_hashnr_caseup(const byte *key, uint len) |
| { |
| const byte *end=key+len; |
| uint hash; |
| for (hash = 0; key < end; key++) |
| { |
| hash *= 16777619; |
| hash ^= (uint) (uchar) toupper(*key); |
| } |
| return (hash); |
| } |
| #endif |
| Mysql中对字符串Hash函数还区分了大小写,我们的测试中使用不区分大小写的字符串Hash函数,另外我们将上面的两个函数分别记为MYSQL_Hash1和MYSQL_Hash2。 |
| 2.4 另一个经验字符串Hash函数 |
| unsigned int hash(char *str) |
| { |
| register unsigned int h; |
| register unsigned char *p; |
| for(h=0, p = (unsigned char *)str; *p ; p++) |
| h = 31 * h + *p; |
| return h; |
| } |
| 3 测试及结果 |
| 3.1 测试说明 |
| 从上面给出的经典字符串Hash函数中可以看出,有的涉及到字符串大小敏感问题,我们的测试中只考虑字符串大小写敏感的函数,另外在上面的函数中有的函数需要长度参数,有的不需要长度参数,这对函数本身的效率有一定的影响,我们的测试中将对函数稍微作一点修改,全部使用长度参数,并将函数内部出现的计算长度代码删除。 |
| 我们用来作测试用的Hash链表采用经典的拉链法解决冲突,另外我们采用静态分配桶(Hash链表长度)的方法来构造Hash链表,这主要是为了简化我们的实现,并不影响我们的测试结果。 |
| 测试文本采用单词表,测试过程中从一个输入文件中读取全部不重复单词构造一个Hash表,测试内容分别是函数总调用次数、函数总调用时间、最大拉链长度、平均拉链长度、桶利用率(使用过的桶所占的比率),其中函数总调用次数是指Hash函数被调用的总次数,为了测试出函数执行时间,该值在测试过程中作了一定的放大,函数总调用时间是指Hash函数总的执行时间,最大拉链长度是指使用拉链法构造链表过程中出现的最大拉链长度,平均拉链长度指拉链的平均长度。 |
| 测试过程中使用的机器配置如下: |
| PIII600笔记本,128M内存,windows 2000 server操作系统。 |
| 3.2 测试结果 |
| 以下分别是对两个不同文本文件中的全部不重复单词构造Hash链表的测试结果,测试结果中函数调用次数放大了100倍,相应的函数调用时间也放大了100倍。 |
|
|
|
|
| 从上表可以看出,这些经典软件虽然构造字符串Hash函数的方法不同,但是它们的效率都是不错的,相互之间差距很小,读者可以参考实际情况从其中借鉴使用。 |
![[转]经典字符串哈希算法 [转]经典字符串哈希算法](http://www.ccw.com.cn/htm/app/aprog/01_8_22_3a.jpg)
![[转]经典字符串哈希算法 [转]经典字符串哈希算法](http://www.ccw.com.cn/htm/app/aprog/01_8_22_3b.jpg)
浙公网安备 33010602011771号