暴雪哈希算法.docx
《暴雪哈希算法.docx》由会员分享,可在线阅读,更多相关《暴雪哈希算法.docx(17页珍藏版)》请在冰豆网上搜索。
暴雪哈希算法
暴雪公司有个经典的字符串的hash公式
先提一个简单的问题,假如有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做?
有一个方法最简单,老老实实从头查到尾,一个一个比较,直到找到为止,我想只要学过程序设计的人都能把这样一个程序作出来,但要是有程序员把这样的程序交给用户,我只能用无语来评价,或许它真的能工作,但...也只能如此了。
最合适的算法自然是使用HashTable(哈希表),先介绍介绍其中的基本知识,所谓Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩"成一个整数,这个数称为Hash,当然,无论如何,一个32位整数是无法对应回一个字符串的,但在程序中,两个字符串计算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法
代码
unsignedlongHashString(char*lpszFileName,unsignedlongdwHashType)
{
unsignedchar*key=(unsignedchar*)lpszFileName;
unsignedlongseed1=0x7FED7FED,seed2=0xEEEEEEEE;
intch;
while(*key!
=0)
{
ch=toupper(*key);
seed1=cryptTable[(dwHashType<<8)ch]^(seed1seed2);
seed2=chseed1seed2(seed2<<5)3;
}
returnseed1;
}
Blizzard的这个算法是非常高效的,被称为"One-WayHash",举个例子,字符串"unitneutralacritter.grp"通过这个算法得到的结果是0xA26067F3。
是不是把第一个算法改进一下,改成逐个比较字符串的Hash值就可以了呢,答案是,远远不够,要想得到最快的算法,就不能进行逐个的比较,通常是构造一个哈希表(HashTable)来解决问题,哈希表是一个大数组,这个数组的容量根据程序的要求来定义,例如1024,每一个Hash值通过取模运算(mod)对应到数组中的一个位置,这样,只要比较这个字符串的哈希值对的位置又没有被占用,就可以得到最后的结果了,想想这是什么速度?
是的,是最快的O
(1),现在仔细看看这个算法吧
代码
intGetHashTablePos(char*lpszString,SOMESTRUCTURE*lpTable,intnTableSize)
{
intnHash=HashString(lpszString),nHashPos=nHash%nTableSize;
if(lpTable[nHashPos].bExists&&!
strcmp(lpTable[nHashPos].pString,lpszString))
returnnHashPos;
else
return-1;//Errorvalue
}
看到此,我想大家都在想一个很严重的问题:
"假如两个字符串在哈希表中对应的位置相同怎么办?
",究竟一个数组容量是有限的,这种可能性很大。
解决该问题的方法很多,我首先想到的就是用"链表",感谢大学里学的数据结构教会了这个百试百灵的法宝,我碰到的很多算法都可以转化成链表来解决,只要在哈希表的每个入口挂一个链表,保存所有对应的字符串就OK了。
事情到此似乎有了完美的结局,假如是把问题独自交给我解决,此时我可能就要开始定义数据结构然后写代码了。
然而Blizzard的程序员使用的方法则是更精妙的方法。
基本原理就是:
他们在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串。
中国有句古话"再一再二不能再三再四",看来Blizzard也深得此话的精髓,假如说两个不同的字符串经过一个哈希算法得到的入口点一致有可能,但用三个不同的哈希算法算出的入口点都一致,那几乎可以肯定是不可能的事了,这个几率是1:
188********478580854784,大概是10的22.3次方分之一,对一个游戏程序来说足够安全了。
现在再回到数据结构上,Blizzard使用的哈希表没有使用链表,而采用"顺延"的方式来解决问题,看看这个算法:
代码
intGetHashTablePos(char*lpszString,MPQHASHTABLE*lpTable,intnTableSize)
{
constintHASH_OFFSET=0,HASH_A=1,HASH_B=2;
intnHash=HashString(lpszString,HASH_OFFSET);
intnHashA=HashString(lpszString,HASH_A);
intnHashB=HashString(lpszString,HASH_B);
intnHashStart=nHash%nTableSize,nHashPos=nHashStart;
while(lpTable[nHashPos].bExists)
{
if(lpTable[nHashPos].nHashA==nHashA&&lpTable[nHashPos].nHashB==nHashB)
returnnHashPos;
else
nHashPos=(nHashPos1)%nTableSize;
if(nHashPos==nHashStart)
break;
}
return-1;//Errorvalue
}
1.计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
2.察看哈希表中的这个位置
3.哈希表中这个位置为空吗?
假如为空,则肯定该字符串不存在,返回
4.假如存在,则检查其他两个哈希值是否也匹配,假如匹配,则表示找到了该字符串,返回
5.移到下一个位置,假如已经越界,则表示没有找到,返回
6.看看是不是又回到了原来的位置,假如是,则返回没找到7.回到3
以下是简单封装
代码
一、类声明头文件
viewplaincopytoclipboardprint?
/////////////////////////////////////////////////////////////////////////////
//Name:
HashAlgo.h
//Purpose:
使用魔兽Hash算法,实现索引表的填充和查找功能。
//Author:
陈相礼
//Modifiedby:
//Created:
07/30/09
//RCS-ID:
$Id:
treetest.h430212009-07-3016:
36:
51ZVZ$
//Copyright:
(C)Copyright2009,TSongCorporation,AllRightsReserved.
//Licence:
/////////////////////////////////////////////////////////////////////////////
#defineMAXFILENAME255//最大文件名长度
#defineMAXTABLELEN1024//默认哈希索引表大小
//////////////////////////////////////////////////////////////////////////
//测试宏定义,正式使用时关闭
#defineDEBUGTEST1
//////////////////////////////////////////////////////////////////////////
//哈希索引表定义
typedefstruct
{
longnHashA;
longnHashB;
boolbExists;
chartest_filename[MAXFILENAME];
//......
}MPQHASHTABLE;
//////////////////////////////////////////////////////////////////////////
//对哈希索引表的算法进行封装
classCHashAlgo
{
public:
#ifDEBUGTEST
longtestid;//测试之用
#endif
CHashAlgo(constlongnTableLength=MAXTABLELEN)//创建指定大小的哈希索引表,不带参数的构造函数创建默认大小的哈希索引表
{
prepareCryptTable();
m_tablelength=nTableLength;
m_HashIndexTable=newMPQHASHTABLE[nTableLength];
for(inti=0;i {
m_HashIndexTable[i].nHashA=-1;
m_HashIndexTable[i].nHashB=-1;
m_HashIndexTable[i].bExists=false;
m_HashIndexTable[i].test_filename[0]='\0';
}
}
voidprepareCryptTable();//对哈希索引表预处理
unsignedlongHashString(char*lpszFileName,unsignedlongdwHashType);//求取哈希值
longGetHashTablePos(char*lpszString);//得到在定长表中的位置
boolSetHashTable(char*lpszString);//将字符串散列到哈希表中
unsignedlongGetTableLength(void);
voidSetTableLength(constunsignedlongnLength);
~CHashAlgo()
{
if(NULL!
=m_HashIndexTable)
{
delete[]m_HashIndexTable;
m_HashIndexTable=NULL;
m_tablelength=0;
}
}
protected:
private:
unsignedlongcryptTable[0x500];
unsignedlongm_tablelength;//哈希索引表长度
MPQHASHTABLE*m_HashIndexTable;
};
/////////////////////////////////////////////////////////////////////////////
//Name:
HashAlgo.h
//Purpose:
使用魔兽Hash算法,实现索引表的填充和查找功能。
//Author:
陈相礼
//Modifiedby:
//Created:
07/30/09
//RCS-ID:
$Id:
treetest.h430212009-07-3016:
36:
51ZVZ$
//Copyright:
(C)Copyright2009,TSongCorporation,AllRightsReserved.
//Licence:
/////////////////////////////////////////////////////////////////////////////
#defineMAXFILENAME255//最大文件名长度
#defineMAXTABLELEN1024//默认哈希索引表大小
//////////////////////////////////////////////////////////////////////////
//测试宏定义,正式使用时关闭
#defineDEBUGTEST1
//////////////////////////////////////////////////////////////////////////
//哈希索引表定义
typedefstruct
{
longnHashA;
longnHashB;
boolbExists;
chartest_filename[MAXFILENAME];
//......
}MPQHASHTABLE;
//////////////////////////////////////////////////////////////////////////
//对哈希索引表的算法进行封装
classCHashAlgo
{
public:
#ifDEBUGTEST
longtestid;//测试之用
#endif
CHashAlgo(constlongnTableLength=MAXTABLELEN)//创建指定大小的哈希索引表,不带参数的构造函数创建默认大小的哈希索引表
{
prepareCryptTable();
m_tablelength=nTableLength;
m_HashIndexTable=newMPQHASHTABLE[nTableLength];
for(inti=0;i {
m_HashIndexTable[i].nHashA=-1;
m_HashIndexTable[i].nHashB=-1;
m_HashIndexTable[i].bExists=false;
m_HashIndexTable[i].test_filename[0]='\0';
}
}
voidprepareCryptTable();//对哈希索引表预处理
unsignedlongHashString(char*lpszFileName,unsignedlongdwHashType);//求取哈希值
longGetHashTablePos(char*lpszString);//得到在定长表中的位置
boolSetHashTable(char*lpszString);//将字符串散列到哈希表中
unsignedlongGetTableLength(void);
voidSetTableLength(constunsignedlongnLength);
~CHashAlgo()
{
if(NULL!
=m_HashIndexTable)
{
delete[]m_HashIndexTable;
m_HashIndexTable=NULL;
m_tablelength=0;
}
}
protected:
private:
unsignedlongcryptTable[0x500];
unsignedlongm_tablelength;//哈希索引表长度
MPQHASHTABLE*m_HashIndexTable;
};
二、类实现文件
viewplaincopytoclipboardprint?
/////////////////////////////////////////////////////////////////////////////
//Name:
HashAlgo.cpp
//Purpose:
使用魔兽Hash算法,实现索引表的填充和查找功能。
//Author:
陈相礼
//Modifiedby:
//Created:
07/30/09
//RCS-ID:
$Id:
treetest.h430212009-07-3016:
36:
51ZVZ$
//Copyright:
(C)Copyright2009,TSongCorporation,AllRightsReserved.
//Licence:
/////////////////////////////////////////////////////////////////////////////
#include"windows.h"
#include"HashAlgo.h"
//////////////////////////////////////////////////////////////////////////
//预处理
voidCHashAlgo:
:
prepareCryptTable()
{
unsignedlongseed=0x00100001,index1=0,index2=0,i;
for(index1=0;index1<0x100;index1++)
{
for(index2=index1,i=0;i<5;i++,index2+=0x100)
{
unsignedlongtemp1,temp2;
seed=(seed*125+3)%0x2AAAAB;
temp1=(seed&0xFFFF)<<0x10;
seed=(seed*125+3)%0x2AAAAB;
temp2=(seed&0xFFFF);
cryptTable[index2]=(temp1|temp2);
}
}
}
//////////////////////////////////////////////////////////////////////////
//求取哈希值
unsignedlongCHashAlgo:
:
HashString(char*lpszFileName,unsignedlongdwHashType)
{
unsignedchar*key=(unsignedchar*)lpszFileName;
unsignedlongseed1=0x7FED7FED,seed2=0xEEEEEEEE;
intch;
while(*key!
=0)
{
ch=toupper(*key++);
seed1=cryptTable[(dwHashType<<8)+ch]^(seed1+seed2);
seed2=ch+seed1+seed2+(seed2<<5)+3;
}
returnseed1;
}
//////////////////////////////////////////////////////////////////////////
//得到在定长表中的位置
longCHashAlgo:
:
GetHashTablePos(char*lpszString)
{
constunsignedlongHASH_OFFSET=0,HASH_A=1,HASH_B=2;
unsignedlongnHash=HashString(lpszString,HASH_OFFSET);
unsignedlongnHashA=HashString(lpszString,HASH_A);
unsignedlongnHashB=HashString(lpszString,HASH_B);
unsignedlongnHashStart=nHash%m_tablelength,
nHashPos=nHashStart;
while(m_HashIndexTable[nHashPos].bExists)
{
if(m_HashIndexTable[nHashPos].nHashA==nHashA&&m_HashIndexTable[nHashPos].nHashB==nHash)
returnnHashPos;
else
nHashPos=(nHashPos+1)%m_tablelength;
if(nHashPos==nHashStart)
break;
}
return-1;//没有找到
}
//////////////////////////////////////////////////////////////////////////
//通过传入字符串,将相应的表项散列到索引表相应位置中去
boolCHashAlgo:
:
SetHashTable(char*lpszString)
{
constunsignedlongHASH_OFFSET=0,HASH_A=1,HASH_B=2;
unsignedlongnHash=HashString(lpszString,HASH_OFFSET);
unsignedlongnHashA=HashString(lpszString,HASH_A);
unsignedlongnHashB=HashString(lpszString,HASH_B);
unsignedlongnHashStart=nHash%m_tablelength,
nHashPos=nHashStart;
while(m_HashIndexTable[nHashPos].bExists)
{
nHashPos=(nHashPos+1)%m_tablelength;
if(nHashPos==nHashStar