暴雪的hash算法
来源:互联网 发布:上海python培训班 编辑:程序博客网 时间:2024/04/29 18:09
值得一提的是,在解决Hash冲突的时候,搞的焦头烂额,结果今天上午在自己的博客内的一篇文章(十一、从头到尾彻底解析Hash表算法)内找到了解决办法:网上流传甚广的暴雪的Hash算法。 OK,接下来,咱们回顾下暴雪的hash表算法:
“接下来,咱们来具体分析一下一个最快的Hash表算法。
我们由一个简单的问题逐步入手:有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做?
有一个方法最简单,老老实实从头查到尾,一个一个比较,直到找到为止,我想只要学过程序设计的人都能把这样一个程序作出来,但要是有程序员把这样的程序交给用户,我只能用无语来评价,或许它真的能工作,但...也只能如此了。
最合适的算法自然是使用HashTable(哈希表),先介绍介绍其中的基本知识,所谓Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩" 成一个整数。当然,无论如何,一个32位整数是无法对应回一个字符串的,但在程序中,两个字符串计算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法:
函数prepareCryptTable以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500]
- //函数prepareCryptTable以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500]
- void prepareCryptTable()
- {
- unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;
- for( index1 = 0; index1 < 0x100; index1++ )
- {
- for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 )
- {
- unsigned long temp1, temp2;
- seed = (seed * 125 + 3) % 0x2AAAAB;
- temp1 = (seed & 0xFFFF) << 0x10;
- seed = (seed * 125 + 3) % 0x2AAAAB;
- temp2 = (seed & 0xFFFF);
- cryptTable[index2] = ( temp1 | temp2 );
- }
- }
- }
函数HashString以下函数计算lpszFileName 字符串的hash值,其中dwHashType 为hash的类型,
- //函数HashString以下函数计算lpszFileName 字符串的hash值,其中dwHashType 为hash的类型,
- unsigned long HashString(const char *lpszkeyName, unsigned long dwHashType )
- {
- unsigned char *key = (unsigned char *)lpszkeyName;
- unsigned long seed1 = 0x7FED7FED;
- unsigned long seed2 = 0xEEEEEEEE;
- int ch;
- while( *key != 0 )
- {
- ch = *key++;
- seed1 = cryptTable[(dwHashType<<8) + ch] ^ (seed1 + seed2);
- seed2 = ch + seed1 + seed2 + (seed2<<5) + 3;
- }
- return seed1;
- }
是不是把第一个算法改进一下,改成逐个比较字符串的Hash值就可以了呢,答案是,远远不够,要想得到最快的算法,就不能进行逐个的比较,通常是构造一个哈希表(Hash Table)来解决问题,哈希表是一个大数组,这个数组的容量根据程序的要求来定义,
例如1024,每一个Hash值通过取模运算 (mod) 对应到数组中的一个位置,这样,只要比较这个字符串的哈希值对应的位置有没有被占用,就可以得到最后的结果了,想想这是什么速度?是的,是最快的O(1),现在仔细看看这个算法吧:
- typedef struct
- {
- int nHashA;
- int nHashB;
- char bExists;
- ......
- } SOMESTRUCTRUE;
- //一种可能的结构体定义?
- //函数GetHashTablePos下述函数为在Hash表中查找是否存在目标字符串,有则返回要查找字符串的Hash值,无则,return -1.
- int GetHashTablePos( har *lpszString, SOMESTRUCTURE *lpTable )
- //lpszString要在Hash表中查找的字符串,lpTable为存储字符串Hash值的Hash表。
- {
- int nHash = HashString(lpszString); //调用上述函数HashString,返回要查找字符串lpszString的Hash值。
- int nHashPos = nHash % nTableSize;
- if ( lpTable[nHashPos].bExists && !strcmp( lpTable[nHashPos].pString, lpszString ) )
- { //如果找到的Hash值在表中存在,且要查找的字符串与表中对应位置的字符串相同,
- return nHashPos; //返回找到的Hash值
- }
- else
- {
- return -1;
- }
- }
然而Blizzard的程序员使用的方法则是更精妙的方法。基本原理就是:他们在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串。”
“MPQ使用文件名哈希表来跟踪内部的所有文件。但是这个表的格式与正常的哈希表有一些不同。首先,它没有使用哈希作为下标,把实际的文件名存储在表中用于验证,实际上它根本就没有存储文件名。而是使用了3种不同的哈希:一个用于哈希表的下标,两个用于验证。这两个验证哈希替代了实际文件名。
当然了,这样仍然会出现2个不同的文件名哈希到3个同样的哈希。但是这种情况发生的概率平均是:1:18889465931478580854784,这个概率对于任何人来说应该都是足够小的。现在再回到数据结构上,Blizzard使用的哈希表没有使用链表,而采用"顺延"的方式来解决问题。”下面,咱们来看看这个网上流传甚广的暴雪hash算法:
函数GetHashTablePos中,lpszString 为要在hash表中查找的字符串;lpTable 为存储字符串hash值的hash表;nTableSize 为hash表的长度:
- //函数GetHashTablePos中,lpszString 为要在hash表中查找的字符串;lpTable 为存储字符串hash值的hash表;nTableSize 为hash表的长度:
- int GetHashTablePos( char *lpszString, MPQHASHTABLE *lpTable, int nTableSize )
- {
- const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
- int nHash = HashString( lpszString, HASH_OFFSET );
- int nHashA = HashString( lpszString, HASH_A );
- int nHashB = HashString( lpszString, HASH_B );
- int nHashStart = nHash % nTableSize;
- int nHashPos = nHashStart;
- while ( lpTable[nHashPos].bExists )
- {
- // 如果仅仅是判断在该表中时候存在这个字符串,就比较这两个hash值就可以了,不用对结构体中的字符串进行比较。
- // 这样会加快运行的速度?减少hash表占用的空间?这种方法一般应用在什么场合?
- if ( lpTable[nHashPos].nHashA == nHashA
- && lpTable[nHashPos].nHashB == nHashB )
- {
- return nHashPos;
- }
- else
- {
- nHashPos = (nHashPos + 1) % nTableSize;
- }
- if (nHashPos == nHashStart)
- break;
- }
- return -1;
- }
上述程序解释:
- 计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
- 察看哈希表中的这个位置
- 哈希表中这个位置为空吗?如果为空,则肯定该字符串不存在,返回-1。
- 如果存在,则检查其他两个哈希值是否也匹配,如果匹配,则表示找到了该字符串,返回其Hash值。
- 移到下一个位置,如果已经移到了表的末尾,则反绕到表的开始位置起继续查询
- 看看是不是又回到了原来的位置,如果是,则返回没找到
- 回到3。
24.4、不重复Hash编码
有了上面的暴雪Hash算法。咱们的问题便可解决了。不过,有两点必须先提醒读者:1、Hash表起初要初始化;2、暴雪的Hash算法对于查询那样处理可以,但对插入就不能那么解决。
关键主体代码如下:
- //函数prepareCryptTable以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500]
- void prepareCryptTable()
- {
- unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;
- for( index1 = 0; index1 <0x100; index1++ )
- {
- for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
- {
- unsigned long temp1, temp2;
- seed = (seed * 125 + 3) % 0x2AAAAB;
- temp1 = (seed & 0xFFFF)<<0x10;
- seed = (seed * 125 + 3) % 0x2AAAAB;
- temp2 = (seed & 0xFFFF);
- cryptTable[index2] = ( temp1 | temp2 );
- }
- }
- }
- //函数HashString以下函数计算lpszFileName 字符串的hash值,其中dwHashType 为hash的类型,
- unsigned long HashString(const char *lpszkeyName, unsigned long dwHashType )
- {
- unsigned char *key = (unsigned char *)lpszkeyName;
- unsigned long seed1 = 0x7FED7FED;
- unsigned long seed2 = 0xEEEEEEEE;
- int ch;
- while( *key != 0 )
- {
- ch = *key++;
- seed1 = cryptTable[(dwHashType<<8) + ch] ^ (seed1 + seed2);
- seed2 = ch + seed1 + seed2 + (seed2<<5) + 3;
- }
- return seed1;
- }
- /////////////////////////////////////////////////////////////////////
- //function: 哈希词典 编码
- //parameter:
- //author: lei.zhou
- //time: 2011-12-14
- /////////////////////////////////////////////////////////////////////
- MPQHASHTABLE TestHashTable[nTableSize];
- int TestHashCTable[nTableSize];
- int TestHashDTable[nTableSize];
- key_list test_data[nTableSize];
- //直接调用上面的hashstring,nHashPos就是对应的HASH值。
- int insert_string(const char *string_in)
- {
- const int HASH_OFFSET = 0, HASH_C = 1, HASH_D = 2;
- unsigned int nHash = HashString(string_in, HASH_OFFSET);
- unsigned int nHashC = HashString(string_in, HASH_C);
- unsigned int nHashD = HashString(string_in, HASH_D);
- unsigned int nHashStart = nHash % nTableSize;
- unsigned int nHashPos = nHashStart;
- int ln, ires = 0;
- while (TestHashTable[nHashPos].bExists)
- {
- // if (TestHashCTable[nHashPos] == (int) nHashC && TestHashDTable[nHashPos] == (int) nHashD)
- // break;
- // //...
- // else
- //如之前所提示读者的那般,暴雪的Hash算法对于查询那样处理可以,但对插入就不能那么解决
- nHashPos = (nHashPos + 1) % nTableSize;
- if (nHashPos == nHashStart)
- break;
- }
- ln = strlen(string_in);
- if (!TestHashTable[nHashPos].bExists && (ln < nMaxStrLen))
- {
- TestHashCTable[nHashPos] = nHashC;
- TestHashDTable[nHashPos] = nHashD;
- test_data[nHashPos] = (KEYNODE *) malloc (sizeof(KEYNODE) * 1);
- if(test_data[nHashPos] == NULL)
- {
- printf("10000 EMS ERROR !!!!\n");
- return 0;
- }
- test_data[nHashPos]->pkey = (char *)malloc(ln+1);
- if(test_data[nHashPos]->pkey == NULL)
- {
- printf("10000 EMS ERROR !!!!\n");
- return 0;
- }
- memset(test_data[nHashPos]->pkey, 0, ln+1);
- strncpy(test_data[nHashPos]->pkey, string_in, ln);
- *((test_data[nHashPos]->pkey)+ln) = 0;
- test_data[nHashPos]->weight = nHashPos;
- TestHashTable[nHashPos].bExists = 1;
- }
- else
- {
- if(TestHashTable[nHashPos].bExists)
- printf("30000 in the hash table %s !!!\n", string_in);
- else
- printf("90000 strkey error !!!\n");
- }
- return nHashPos;
- }
接下来要读取索引文件big_index对其中的关键词进行编码(为了简单起见,直接一行一行扫描读写,没有跳过行数了):
- void bigIndex_hash(const char *docpath, const char *hashpath)
- {
- FILE *fr, *fw;
- int len;
- char *pbuf, *p;
- char dockey[TERM_MAX_LENG];
- if(docpath == NULL || *docpath == '\0')
- return;
- if(hashpath == NULL || *hashpath == '\0')
- return;
- fr = fopen(docpath, "rb"); //读取文件docpath
- fw = fopen(hashpath, "wb");
- if(fr == NULL || fw == NULL)
- {
- printf("open read or write file error!\n");
- return;
- }
- pbuf = (char*)malloc(BUFF_MAX_LENG);
- if(pbuf == NULL)
- {
- fclose(fr);
- return ;
- }
- memset(pbuf, 0, BUFF_MAX_LENG);
- while(fgets(pbuf, BUFF_MAX_LENG, fr))
- {
- len = GetRealString(pbuf);
- if(len <= 1)
- continue;
- p = strstr(pbuf, "#####");
- if(p != NULL)
- continue;
- p = strstr(pbuf, " ");
- if (p == NULL)
- {
- printf("file contents error!");
- }
- len = p - pbuf;
- dockey[0] = 0;
- strncpy(dockey, pbuf, len);
- dockey[len] = 0;
- int num = insert_string(dockey);
- dockey[len] = ' ';
- dockey[len+1] = '\0';
- char str[20];
- itoa(num, str, 10);
- strcat(dockey, str);
- dockey[len+strlen(str)+1] = '\0';
- fprintf (fw, "%s\n", dockey);
- }
- free(pbuf);
- fclose(fr);
- fclose(fw);
- }
主函数已经很简单了,如下:
- int main()
- {
- prepareCryptTable(); //Hash表起初要初始化
- //现在要把整个big_index文件插入hash表,以取得编码结果
- bigIndex_hash("big_index.txt", "hashpath.txt");
- system("pause");
- return 0;
- }
- 暴雪的hash算法
- 暴雪hash算法
- 暴雪hash算法实例
- 打造最快的Hash表(暴雪的hash算法)
- Hash冲突的解决--暴雪的Hash算法
- Hash冲突的解决--暴雪的Hash算法
- Hash冲突的解决--暴雪的Hash算法
- 最快的内容查找算法-----暴雪的Hash算法
- 最快的内容查找算法-----暴雪的Hash算法
- 最快的内容查找算法-----暴雪的Hash算法
- 暴雪公司关于字符串匹配的hash算法
- 暴雪公司关于字符串匹配的hash算法
- 暴雪公司关于字符串匹配的hash算法
- 暴雪公司关于字符串匹配的hash算法
- 暴雪公司关于字符串匹配的hash算法
- 暴雪公司关于字符串匹配的hash算法
- 21Hash算法以及暴雪Hash
- 暴雪hash来作整数的hash
- 秒杀多线程系列
- Windows上多线程同步相关的MFC类(2)
- buffer cache深度分析1:概念以及内存结构
- hbase开发者命令和API
- 如何在Linux下安装启动多个Tomcat
- 暴雪的hash算法
- AsIHttprequest缓存
- Objective-C语法快速参考
- SVN、CVS、VSS区别
- Ajax js 使用Ajax检测用户名是否存在
- sysfs API总结
- Hdu 2042 - 不容易系列之二
- ASP.NET MVC3.0简单入门(0)
- 11g cursor_sharing 参数说明