暴雪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算法原理之前,我们先来了解一下这几 ...
随机推荐
- JSON操作技巧
JSONObject.JSONArray 最近两个星期接触最多的就是json和map了. 之前用到的json,就是一个键对应一个值,超级简单的一对一关系.现在用到的json那可以层层嵌套啊,刚开始接触 ...
- 74. Search a 2D Matrix
题目: Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the f ...
- Spring中的mappingResources和mappingDirectoryLocations
今天使用Spring+Hibernate进行事务管理,按照顺序也就是配置,DataSource,Sessionfactory,事务管理器以及拦截器. DateSource可以直接使用Hibernate ...
- sudo
sudo的目的:为非根用户授予根用户的权限: 配置文件:/etc/sudoers visudo命令编辑修改/etc/sudoers配置文件 1.一般用户赋权设置: [root@localhost ~] ...
- Android使用Fragment程序崩溃
调用Fragment的Activity要继承并实现Fragment.OnFragmentInteractionListener
- Android Activity形象描述
Activity就是形象的说就是一个容器,在里面放置各种控件(按钮,文本,复选框等),就形成了软件的界面~ Activity是可见的,如果不加任何控件的话,那么就像Windows中的空白窗体一样 通过 ...
- 国内顺利使用Google的另类技巧
在特殊的地方和特殊的时间,流畅顺利使用Google的方法也会变得很特殊.本文不定期保持维护更新,删除不能用的,增加新的网址. 分享一些奇葩的Google使用方法,通过下列网址也可以使用Google来搜 ...
- 在oracle中where 子句和having子句中的区别
在oracle中where 子句和having子句中的区别 1.where 不能放在GROUP BY 后面 2.HAVING 是跟GROUP BY 连在一起用的,放在GROUP BY 后面,此时的作用 ...
- Codeforces Round #206 (Div. 1)B(记忆化)
这题刚开始理解错题意了 以为只能往右和下走 这题挺好的 看题解看了N久啊 二维的DP 第一维表示走到第几步 可以画一个正方形 以左上角斜着划线 第i步走的点只能是第i条线上的点 而dp的第二维 就表示 ...
- autofac meta
http://kevincuzner.com/2014/05/19/extreme-attributed-metadata-autofac/ http://stackoverflow.com/ques ...