暴雪HASH算法(转)
暴雪公司有个经典的字符串的hash公式
先提一个简单的问题,假如有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做?
有一个方法最简单,老老实实从头查到尾,一个一个比较,直到找到为止,我想只要学过程序设计的人都能把这样一个程序作出来,但要是有程序员把这样的程序交给用户,我只能用无语来评价,或许它真的能工作,但也只能如此了。
最合适的算法自然是使用HashTable(哈希表),先介绍介绍其中的基本知识,所谓Hash,一般是一个整数,通过某种算法,可以把一个字符串"压缩" 成一个整数,这个数称为Hash,当然,无论如何,一个32位整数是无法对应回一个字符串的,但在程序中,两个字符串计算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法
unsigned long HashString(char *lpszFileName, unsigned long dwHashType)
{
unsigned char *key = (unsigned char *)lpszFileName;
unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;
int ch;
while(*key != )
{
ch = toupper(*key++ );
seed1 = cryptTable[(dwHashType << ) + ch] ^ (seed1 + seed2);
seed2 = ch + seed1 + seed2 + (seed2 << ) + ;
}
return seed1;
}
Blizzard的这个算法是非常高效的,被称为"One-Way Hash",举个例子,字符串"unitneutralacritter.grp"通过这个算法得到的结果是0xA26067F3。
是不是把第一个算法改进一下,改成逐个比较字符串的Hash值就可以了呢,答案是,远远不够,要想得到最快的算法,就不能进行逐个的比较,通常是构造一个哈希表(Hash Table)来解决问题,哈希表是一个大数组,这个数组的容量根据程序的要求来定义,例如1024,每一个Hash值通过取模运算 (mod)对应到数组中的一个位置,这样,只要比较这个字符串的哈希值对应的位置又没有被占用,就可以得到最后的结果了,想想这是什么速度?是的,是最快的O(1),现在仔细看看这个算法吧
int GetHashTablePos(char *lpszString, SOMESTRUCTURE *lpTable, int nTableSize)
{
int nHash = HashString(lpszString), nHashPos = nHash % nTableSize;
if (lpTable[nHashPos].bExists && !strcmp(lpTable[nHashPos].pString, lpszString))
return nHashPos;
else
return -; //Error value
}
看到此,我想大家都在想一个很严重的问题:"假如两个字符串在哈希表中对应的位置相同怎么办?",究竟一个数组容量是有限的,这种可能性很大。解决该问题的方法很多,我首先想到的就是用"链表",感谢大学里学的数据结构教会了这个百试百灵的法宝,我碰到的很多算法都可以转化成链表来解决,只要在哈希表的每个入口挂一个链表,保存所有对应的字符串就OK了。
事情到此似乎有了完美的结局,假如是把问题独自交给我解决,此时我可能就要开始定义数据结构然后写代码了。然而Blizzard的程序员使用的方法则是更精妙的方法。基本原理就是:他们在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串。
中国有句古话"再一再二不能再三再四",看来Blizzard也深得此话的精髓,假如说两个不同的字符串经过一个哈希算法得到的入口点一致有可能,但用三个不同的哈希算法算出的入口点都一致,那几乎可以肯定是不可能的事了,这个几率是1:18889465931478580854784,大概是10的 22.3次方分之一,对一个游戏程序来说足够安全了。
现在再回到数据结构上,Blizzard使用的哈希表没有使用链表,而采用"顺延"的方式来解决问题,看看这个算法:
int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize)
{
const int HASH_OFFSET = , HASH_A = , HASH_B = ;
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)
{
if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB)
return nHashPos;
else
nHashPos = (nHashPos + ) % nTableSize;
if (nHashPos == nHashStart)
break;
}
return -; //Error value
}
1. 计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
2. 察看哈希表中的这个位置
3. 哈希表中这个位置为空吗?假如为空,则肯定该字符串不存在,返回
4. 假如存在,则检查其他两个哈希值是否也匹配,假如匹配,则表示找到了该字符串,返回
5. 移到下一个位置,假如已经越界,则表示没有找到,返回
6. 看看是不是又回到了原来的位置,假如是,则返回没找到
7. 回到3
怎么样,很简单的算法吧,但确实是天才的idea, 其实最优秀的算法往往是简单有效的算法。
附上完整的算法代码:
/*********************************StringHash.h*********************************/ #pragma once #define MAXTABLELEN 1024 // 默认哈希索引表大小
//////////////////////////////////////////////////////////////////////////
// 哈希索引表定义
typedef struct _HASHTABLE
{
long nHashA;
long nHashB;
bool bExists;
}HASHTABLE, *PHASHTABLE ; class StringHash
{
public:
StringHash(const long nTableLength = MAXTABLELEN);
~StringHash(void);
private:
unsigned long cryptTable[0x500];
unsigned long m_tablelength; // 哈希索引表长度
HASHTABLE *m_HashIndexTable;
private:
void InitCryptTable(); // 对哈希索引表预处理
unsigned long HashString(const string& lpszString, unsigned long dwHashType); // 求取哈希值
public:
bool Hash(string url);
unsigned long Hashed(string url); // 检测url是否被hash过
}; /*********************************StringHash.cpp*********************************/ #include "StdAfx.h"
#include "StringHash.h" StringHash::StringHash(const long nTableLength /*= MAXTABLELEN*/)
{
InitCryptTable();
m_tablelength = nTableLength;
//初始化hash表
m_HashIndexTable = new HASHTABLE[nTableLength];
for ( int i = ; i < nTableLength; i++ )
{
m_HashIndexTable[i].nHashA = -;
m_HashIndexTable[i].nHashB = -;
m_HashIndexTable[i].bExists = false;
}
} StringHash::~StringHash(void)
{
//清理内存
if ( NULL != m_HashIndexTable )
{
delete []m_HashIndexTable;
m_HashIndexTable = NULL;
m_tablelength = ;
}
} /************************************************************************/
/*函数名:InitCryptTable
/*功 能:对哈希索引表预处理
/*返回值:无
/************************************************************************/
void StringHash::InitCryptTable()
{
unsigned long seed = 0x00100001, index1 = , index2 = , i; for( index1 = ; index1 < 0x100; index1++ )
{
for( index2 = index1, i = ; i < ; i++, index2 += 0x100 )
{
unsigned long temp1, temp2;
seed = (seed * + ) % 0x2AAAAB;
temp1 = (seed & 0xFFFF) << 0x10;
seed = (seed * + ) % 0x2AAAAB;
temp2 = (seed & 0xFFFF);
cryptTable[index2] = ( temp1 | temp2 );
}
}
} /************************************************************************/
/*函数名:HashString
/*功 能:求取哈希值
/*返回值:返回hash值
/************************************************************************/
unsigned long StringHash::HashString(const string& lpszString, unsigned long dwHashType)
{
unsigned char *key = (unsigned char *)(const_cast<char*>(lpszString.c_str()));
unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;
int ch; while(*key != )
{
ch = toupper(*key++); seed1 = cryptTable[(dwHashType << ) + ch] ^ (seed1 + seed2);
seed2 = ch + seed1 + seed2 + (seed2 << ) + ;
}
return seed1;
} /************************************************************************/
/*函数名:Hashed
/*功 能:检测一个字符串是否被hash过
/*返回值:如果存在,返回位置;否则,返回-1
/************************************************************************/
unsigned long StringHash::Hashed(string lpszString) {
const unsigned long HASH_OFFSET = , HASH_A = , HASH_B = ;
//不同的字符串三次hash还会碰撞的几率无限接近于不可能
unsigned long nHash = HashString(lpszString, HASH_OFFSET);
unsigned long nHashA = HashString(lpszString, HASH_A);
unsigned long nHashB = HashString(lpszString, HASH_B);
unsigned long nHashStart = nHash % m_tablelength,
nHashPos = nHashStart; while ( m_HashIndexTable[nHashPos].bExists)
{
if (m_HashIndexTable[nHashPos].nHashA == nHashA && m_HashIndexTable[nHashPos].nHashB == nHashB)
return nHashPos;
else
nHashPos = (nHashPos + ) % m_tablelength; if (nHashPos == nHashStart)
break;
} return -; //没有找到
} /************************************************************************/
/*函数名:Hash
/*功 能:hash一个字符串
/*返回值:成功,返回true;失败,返回false
/************************************************************************/
bool StringHash::Hash(string lpszString)
{
const unsigned long HASH_OFFSET = , HASH_A = , HASH_B = ;
unsigned long nHash = HashString(lpszString, HASH_OFFSET);
unsigned long nHashA = HashString(lpszString, HASH_A);
unsigned long nHashB = HashString(lpszString, HASH_B);
unsigned long nHashStart = nHash % m_tablelength,
nHashPos = nHashStart; while ( m_HashIndexTable[nHashPos].bExists)
{
nHashPos = (nHashPos + ) % m_tablelength;
if (nHashPos == nHashStart) //一个轮回
{
//hash表中没有空余的位置了,无法完成hash
return false;
}
}
m_HashIndexTable[nHashPos].bExists = true;
m_HashIndexTable[nHashPos].nHashA = nHashA;
m_HashIndexTable[nHashPos].nHashB = nHashB; return true;
}
暴雪HASH算法(转)的更多相关文章
- 暴雪hash算法
你有一个非常大的字符串数组A,现在又有一个字符串B,需要你去检测B是否存在于A中.最简单粗暴的方法是遍历整个A,但是这个方法投入到实际应用时的运行速度是难以接受的.在没有与其他所有字符串比较前怎么知道 ...
- 暴雪的hash算法[翻译]
原文来自:http://sfsrealm.hopto.org/inside_mopaq/chapter2.htm#hashes 促进历史进步的大多数契机都是在解决特定问题的过程中产生的,本文讨论一下M ...
- Hash冲突的解决--暴雪的Hash算法
Hash冲突的解决--暴雪的Hash算法https://usench.iteye.com/blog/2199399https://www.bbsmax.com/A/kPzOO7a8zx/
- 21Hash算法以及暴雪Hash
一:哈希表简介 哈希表是一种查找效率极高的数据结构,理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值(key),然后在常量时间内 ...
- 记录几个经典的字符串hash算法
记录几个经典的字符串hash算法,方便以后查看: 推荐一篇文章: http://www.partow.net/programming/hashfunctions/# (1)暴雪字符串hash #inc ...
- 对一致性Hash算法,Java代码实现的深入研究
一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...
- 一致性hash算法详解
转载请说明出处:http://blog.csdn.net/cywosp/article/details/23397179 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT) ...
- 一致性hash算法简介
一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希 ...
- 分布式缓存技术memcached学习(四)—— 一致性hash算法原理
分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...
随机推荐
- 高德地图API编译警告
版本: V2.1.1 警告内容: (null): warning: (i386) /Users/xiaominghan/Desktop/autonavi/MAMapKit_3D_r923_201310 ...
- c#用正则表达式判断字符串是否全是数字、小数点、正负号组成 Regex reg = new Regex(@"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$");
Regex reg = new Regex(@"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][ ...
- c#.net中参数修饰符ref,out ,params解析
params ============================================================================================= ...
- ASP.NET 4.5.256 has not been registered on the Web server. You need to manually configure your Web server for ASP.NET 4.5.256 in order for your site to run correctly
Microsoft .NET Framework 4.6安装后,用户可能会在使用Microsoft Visual Studio 创建(或打开现有项目时)网站.或Windows Azure项目时遇到下面 ...
- Blend 2015 教程 (三) 模板
前一篇讲述了一些基本样式的修改方法,并搭建了Style层的基本框架,本篇将进一步修改ListBox的样式. 1. 首先选中ListBox控件,在美工板导航栏中点击ListBox,选择 编辑其他模板-编 ...
- Actor的原理
先从著名的c10k问题谈起.有一个叫Dan Kegel的人在网上(http://www.kegel.com/c10k.html)提出:现在的硬件应该能够让一台机器支持10000个并发的client.然 ...
- UWP?UWP! - Build 2015有些啥?(2)
UWP?UWP! - Build 2015有些啥? Build 2015圆满落幕了,不知大家有多少人刷夜看了直播呢?不管怎么说,想必各位都很好奇在这场微软开发者盛宴上,Microsoft又发布了什么令 ...
- java提高篇(十三)-----equals()方法总结
equals() 超类Object中有这个equals()方法,该方法主要用于比较两个对象是否相等.该方法的源码如下: public boolean equals(Object obj) { retu ...
- onSingleTapUp()和onSingleTapConfirmed()的区别
onSingleTapUp() - 在按下并抬起时发生,只要符合这个条件就触发该函数,没有任何附加条件. onSingleTapConfirmed() 同上者,但有附加条件,就是Android会确保单 ...
- springmvc下js控制表单提交(表单提交前检验,提交后获取json返回值)
这个问题我搞了四天,终于搞懂.因为对js很不熟悉.郁闷的是后台代码出错总可以设置断点调试,前端js代码出错只能通过浏览器提供一些运行数据来分析,很不习惯. 首先说下逻辑:这是一个注册功能,我希望,注册 ...