Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1682935
  • 博文数量: 311
  • 博客积分: 7778
  • 博客等级: 少将
  • 技术积分: 4186
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-09 19:59
个人简介

蓝点工坊(http://www.bluedrum.cn) 创始人,App和嵌入式产品开发。同时也做相应培训和外包工作。 详细介绍 http://pan.baidu.com/s/1y2g88

文章存档

2012年(3)

2011年(115)

2010年(170)

2009年(23)

分类: C/C++

2010-07-16 23:05:27

Andrew Huang
 
 
一.哈希表
------------------------------------------------------------------------------
 
  哈希是hash的音译,有时也意译成散列。它定义了一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法。由于通过更短的哈希值比用原始值进行数据库搜索更快,这种方法一般用来在数据库中建立索引并进行搜索,同时还用在各种解密算法中。
   正因为哈希有快速检索和加密两大功能。因此在编程中大量应用哈希算法。
 
   哈希的原理比较容易懂。我们看一个Hash表例子:
   
   有一组数据包括用户名字、电话、住址等,为了快速的检索,我们可以利用名字作为关键码,hash规则就是把名字中每一个字的拼音的第一个字母拿出来,把该字母在26个字母中的顺序值取出来加在一块作为改记录的地址。比如张三,就是z+s=26+19=45。就是把张三存在地址为45处。
 
   但是这样存在一个问题,比如假如有个用户名字叫做:周四,那么计算它的地址时也是z+s=45,这样它与张三就有相同的地址,这就是冲突,也叫作碰撞!
 
二.不同系统哈希算法
------------------------------------------------------
  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 OpenSSL_Hash1(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);
}

 

/* The following hash seems to work very well on normal text strings
* no collisions on /usr/dict/words and it distributes on %2^n quite
* well, not as good as MD5, but still good.
*/

unsigned long OpenSSL_Hash2(const char *c)
{
unsigned long ret=0;
long n;
unsigned long v;
int r;
 
if ((c == NULL) || (*c == '\0'))
return(ret);
/*
unsigned char b[16];
MD5(c,strlen(c),b);
return(b[0]|(b[1]<<8)|(b[2]<<16)|(b[3]<<24));
*/

 
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);
}
 

 
2.3 MySql中出现的字符串Hash函数
 

/* Calc hash for a key */
 
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);
}
 
/* Calc hash for a key, case indepenently */
 
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);
}

 
Mysql中对字符串Hash函数还区分了大小写,我们的测试中使用不区分大小写的字符串Hash函数,另外我们将上面的两个函数分别记为MYSQL_Hash1和MYSQL_Hash2。
 

/*
* Fowler/Noll/Vo hash
*
* The basis of the hash algorithm was taken from an idea sent by email to the
* IEEE Posix P1003.2 mailing list from Phong Vo (kpv@research.att.com) and
* Glenn Fowler (gsf@research.att.com). Landon Curt Noll (chongo@toad.com)
* later improved on their algorithm.
*
* The magic is in the interesting relationship between the special prime
* 16777619 (2^24 + 403) and 2^32 and 2^8.
*
* This hash produces the fewest collisions of any that we've seen so
* far, and works well on both numbers and strings.
*/

 
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);
}

 
 
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;
}

 
2.5 各个HASH算法测试比较
以下分别是对两个不同文本文件中的全部不重复单词构造Hash链表的测试结果,测试结果中函数调用次数放大了100倍,相应的函数调用时间也放大了100倍。
 
 
 
 
从上表可以看出,这些经典软件虽然构造字符串Hash函数的方法不同,但是它们的效率都是不错的,相互之间差距很小,读者可以参考实际情况从其中借鉴使用。
 
三.哈希表冲突解决办法
--------------------------------------------------------------------------
   哈希算法不可避免要碰到两组值得到HASH值一样的情况,如何解决冲突,有常见几种算法
  1、开放定址法.
      即按如下公式重新把HASH值再次做运算
      hi=(h(key)+di) mod m i=1,2,...,k(k<=m-1).
    其中di值取 1,2,3....
 
  2、二次探查法(quadratic probing)
       上述公式的值中di可以取1,-1,2,-2,4,-4...
     
  3.双重散列法。
       即做两个HASH函数,求值后再次运算.两次Hash重复值可能性大大降低
      hi=(h(key)+i*h1(key)) %m 其中 0≤i≤m-1
 
  4.、拉链法
    拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为   m,则可将散列表定义为一个由m个头指针组成的指针数组t[0..m-1]。凡是散列地址为i的结点,均插入到以t为头指针的单链表中。
 
  
 
四,暴雪游戏的hash算法
-----------------------------------------------------------------------
 
魔兽(Blizzard)文件打包管理器的相关算法,即MPQ文件索引表的生成和查找采用哈希表进行,在冲突方面的处理方面,采用线性探测再散列。在添加和查找过程中进行了三次哈希,第一个哈希值用来查找,后两个哈希值用来校验,这样可以大大减少冲突的几率.
 
哈希算法

unsigned long HashString(char *lpszFileName, unsigned long dwHashType)
{
 unsigned char *key = (unsigned char *)lpszFileName;
unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;
int ch;

while(*key != 0)
{
  ch = toupper(*key++);

seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
 }
return seed1;
}


 
 
其冲突查找算法:这个方法非常快,没有使用链表,而是用顺延的方式
 
1. 计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
2. 察看哈希表中的这个位置
3. 哈希表中这个位置为空吗?如果为空,则肯定该字符串不存在,返回
4. 如果存在,则检查其他两个哈希值是否也匹配,如果匹配,则表示找到了该字符串,返回
5. 移到下一个位置,如果已经越界,则表示没有找到,返回
6. 看看是不是又回到了原来的位置,如果是,则返回没找到
7. 回到3
 

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, nHashPos = nHashStart;

while (lpTable[nHashPos].bExists)
{
//比较的是Table中存储的另外两个Hash函数的值,Table中不存储字符串?

  if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB)
   return nHashPos;
  else
//冲突处理

   nHashPos = (nHashPos + 1) % nTableSize;
  
  if (nHashPos == nHashStart)
   break;
 }

return -1;
//Error value

}


 
五。一些标准哈希算法
 
参考资料
---------------------------------

 

 









阅读(1966) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~