暴雪hash算法
你有一个非常大的字符串数组A,现在又有一个字符串B,需要你去检测B是否存在于A中。最简单粗暴的方法是遍历整个A,但是这个方法投入到实际应用时的运行速度是难以接受的。在没有与其他所有字符串比较前怎么知道该字符串是否存在呢?
解决方法是使用哈希表,即用较小的数据类型来代表较大的数据类型,例如:用数字来代表字符串。你可以存储哈希值与字符串一一对应,当需要检测一个字符串时,就用哈希算法计算其哈希值,然后与存储的哈希值比较级可以得出结果,使用这一方法根据数组的大小和字符串长度提升速度大约100倍。
unsigned long HashString(char *lpszString)
{
unsigned long ulHash = 0xf1e2d3c4;
while (*lpszString != )
{
ulHash <<= ;
ulHash += *lpszString++;
}
return ulHash;
}
上述代码演示了一个简单的哈希算法。该函数在遍历整个字符串时,将ulHash左移一位再叫上字符值。使用这个算法 ,"arr\units.dat" 的哈希值是0x5A858026,字符串"unit\neutral\acritter.grp" 的哈希值是0x694CD020。但是这个算法没有什么使用价值,因为它产生的哈希值是可以预测的,可能使不同的字符产生相同的哈希值,从而产生碰撞。
要解决这一问题方法,网上流传最神的是MPQ,源自于暴雪公司的文件打包管理,用于Blizzard游戏的数据文件,包括图形,声音,等级等数据,该算法能够压缩,解密,文件分割等功能。详情见维基:http://en.wikipedia.org/wiki/MPQ。
该算法产生的哈希值完全无法预测,非常高效,被称为"One-Way Hash"( A one-way hash is a an algorithm that is constructed in such a way that deriving the original string (set of strings, actually) is virtually impossible)。
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;
}
尝试在前面的示例中使用相同索引,您的程序一定会有中断现象发生,而且不够快。如果想让它更快,您能做的只有让程序不去查询数组中的所有散列值。或者您可以只做一次对比就可以得出在列表中是否存在字符串。听起来不错,真的么?不可能的啦
一个哈希表就是以字符串的哈希值作为下标的一类数组。我的意思是,哈希表使用一个固定长度的字符串数组(比如1024,2的偶次幂)进行存储;当你要看看这个字符串是否存在于哈希表中,为了获取这个字符串在哈希表中的位置,你首先计算字符串的哈希值,然后哈希表的长度取模。这样如果你像上一节那样使用简单的哈希算法,字符串"arr\units.dat" 的哈希值是0x5A858026,偏移量0x26(0x5A858026 除于0x400等于0x16A160,模0x400等于0x26)。因此,这个位置的字符串将与新加入的字符串进行比较。如果0X26处的字符串不匹配或不存在,那么表示新增的字符串在数组中不存在。下面是示意的代码:
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
}
上面的说明中存在一个缺陷。当有冲突(两个不同的字符串有相同的哈希值)发生的时候怎么办?显而易见的,它们不能占据哈希表中的同一个位置。通常的解决办法是为每一个哈希值指向一个链表,用于存放所有哈希冲突的值;
MPQs使用一个存放文件名的哈希表来跟踪文件内部,但是表的格式与通常方法有点不同,首先不像通常的做法使用哈希值作为偏移量,存储实际的文件名。MPQs 根本不存储文件名,而是使用了三个不同的哈希值:一个用做哈希表偏移量,两个用作核对。这两个核对的哈希值用于替代文件名。当然从理论上说存在两个不同的文件名得到相同的三个哈希值,但是这种情况发送的几率是:1:18889465931478580854784,这应该足够安全了。
MPQ's的哈希表的实现与传统实现的另一个不同的地方是,相对与传统做法(为每个节点使用一个链表,当冲突发生的时候,遍历链表进行比较),看一下下面的示范代码,在MPQ中定位一个文件进行读操作:
int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize)
{
const int HASH_OFFSET = , HASH_A = , HASH_B = ;
int nHash = HashString(lpszString, HASH_OFFSET),nHashA = HashString(lpszString, HASH_A),nHashB = HashString(lpszString, HASH_B), 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步;
如果你注意的话,可能已经从我们的解释和示例代码注意到,MPQ的哈希表已经将所有的文件入口放入MPQ中;那么当哈希表的每个项都被填充的时候,会发生什么呢?答案可能会让你惊讶:你不能添加任何文件。有些人可能会问我为什么文件数量上有这样的限制(文件限制),是否有办法绕过这个限制?就此而言,如果不重新创建MPQ 的项,甚至无法调整哈希表的大小。这是因为每个项在哈希表中的位置会因为跳闸尺寸而改变,而我们无法得到新的位置,因为这些位置值是文件名的哈希值,而我们根本不知道文件名是什么。
如果想要深入了解MPQ入此坑: http://sfsrealm.hopto.org/inside_mopaq/index.htm
暴雪hash算法的更多相关文章
- 暴雪HASH算法(转)
暴雪公司有个经典的字符串的hash公式 先提一个简单的问题,假如有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做? 有一个方法最简单,老老实实 ...
- 暴雪的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算法原理之前,我们先来了解一下这几 ...
随机推荐
- sed and awk学习笔记
sed and awk 背景 awk起源追溯至sed和grep,进而追溯至共同的行编辑器ed.实用工具grep来源于ed命令:g/re/p .实用工具awk和sed有一个共同的选项-f用于指定脚本的名 ...
- Java API —— Random类
1.Random类概述 此类用于产生随机数 如果用相同的种子创建两个 Random 实例,则对每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列. 2.构造 ...
- linux用VSFTP搭建FTP服务器
一般在各种linux的发行版中,默认带有的ftp软件是vsftp,从各个linux发行版对vsftp的认可可以看出,vsftp应该是一款不错的ftp软件. sudo apt-get install v ...
- 特殊的css样式
在一定范围大小变化的div .div { width:auto; height:auto; min-height:100px; min-width:100px; max-height:200px; m ...
- android中最先被执行的activity
像C.C++.JAVA都有一个主函数作为程序的入口点,但是Android中并没有一个明确的主窗口,那么在有多个Activity的情况下,最先被执行的是哪个呢?这完全取决于配置文件AndroidMain ...
- java 语言里 遍历 collection 的方式
我来简单说一下吧,一般有2种方法来遍历collection中的元素,以HashSet为例子HashSet hs=new HashSet();hs.add("hello");hs.a ...
- bzoj1563
P<=10一开始是吓死我了 后来想到这就是一个经典的决策单调性解决1d1d动态规划的题目 像决策单调性完全可以打表找规律,这里有一篇严谨的证明https://www.byvoid.com/blo ...
- bzoj1296: [SCOI2009]粉刷匠
dp. 用到俩次dp,用1和0代表俩种颜色,首先对于每块木板我们进行一次dp,g[i][j]代表前j个格子刷i次最多能涂到几个格子. 则 g[i][j]=max(g[i-1][k],max(cnt[j ...
- SharePoint CMAL方式处理的 增,删,查,改
SPContext.Current.Web.Lists["UserInfo"]:获取网站的List,名称是:UserInfo userlist.AddItem():添加数据到Lis ...
- UVa 10375 (唯一分解定理) Choose and divide
题意: 求组合数C(p, q) / C(r, s)结果保留5为小数. 分析: 先用筛法求出10000以内的质数,然后计算每个素数对应的指数,最后再根据指数计算答案. #include <cstd ...