Blizzard的MPQ文件格式搜索算法

Blizzard被称为最卓越的游戏制作公司,不愧于此。

作者:ghost2006

最近学习了一下Blizzard的MPQ文件格式,颇有一些心得,其中一条就是对HastTable的理解,很想写出来给大家共享,感谢Justin Olbrantz的文章《Inside MoPaQ》,大多认识来源于此。

先提一个简单的问题,如果有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做?有一个方法最简单,老老实实从头查到尾,一个一个比较,直到找到为止,我想只要学过程序设计的人都能把这样一个程序作出来,但要是有程序员把这样的程序交给用户,我只
能用无语来评价,或许它真的能工作,但...也只能如此了。最合适的算法自然是使用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 != 0)

ch = toupper(*key++);
seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3; 
}
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 -1; //Error value 

看 到此,我想大家都在想一个很严重的问题:"如果两个字符串在哈希表中对应的位置相同怎么办?",毕竟一个数组容量是有限的,这种可能性很大。解决该问题的 方法很多,我首先想到的就是用"链表",大学里学的数据结构教会了这个百试百灵的法宝,我遇到的很多算法都可以转化成链表来解决,只要在哈希表的每个 入口挂一个链表,保存所有对应的字符串就OK了。事情到此似乎有了完美的结局,如果是把问题独自交给我解决,此时我可能就要开始定义数据结构然后写代码了。然而Blizzard的程序员使用的方法则是更精妙的方法。基本原理就是:他们在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串。中 国有句古话"再一再二不能再三再四",看来Blizzard也深得此话的精髓,如果说两个不同的字符串经过一个哈希算法得到的入口点一致有可能,但用三个 不同的哈希算法算出的入口点都一致,那几乎可以肯定是不可能的事了,这个几率是:1888946593147858085478
4,大概是10的 22.3次方分之一,对一个游戏程序来说足够安全了。现在再回到数据结构上,Blizzard使用的哈希表没有使用链表,而采用"顺延"的方式来解决问题,看看这个算法:
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)

if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB

return nHashPos; 
else 
nHashPos = (nHashPos + 1) % nTableSize;

if (nHashPos == nHashStart) 
break; 
}
return -1; //Error value 

1. 计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
2. 察看哈希表中的这个位置
3. 哈希表中这个位置为空吗?如果为空,则肯定该字符串不存在,返回
4. 如果存在,则检查其他两个哈希值是否也匹配,如果匹配,则表示找到了该字符串,返

5. 移到下一个位置,如果已经越界,则表示没有找到,返回
6. 看看是不是又回到了原来的位置,如果是,则返回没找到
7. 回到3
怎么样,很简单的算法吧,但确实是天才的idea, 其实最优秀的算法往往是简单有效的算法.

來自:Ngacn.com

Re:Blizzard的MPQ文件格式搜索算法

晕死,其实这篇文章最早还是我在bokee.com上写的,转来转去哪儿都有了 :)

不过这篇文章现在看起来已经很土了,而且还有一些观点是不妥的,正好纠正一下

1。解决hash表碰撞有很多方法,最开始提到的链表(chaining)是其中一种,这种方法并不是我原先认为的“糟糕”的方法,相反,这种方法是最普遍的,C++标准库中的hash表用的正是这种方法。

2. 暴雪没有使用链表,而是使用线性挖掘方式(Linear probing),这是由于这种不需要动态分配内存,而且更方便将数据序列化到磁盘文件上。

2. 更好的方法是"幂次挖掘"方法(quadratic probing),发生碰撞时,按照"+1,-1,+4,-4,+9,-9,...,+n^2,-n^2"方式移动,这种方式下还需要要求容器的长度是质数,可以达到更快的速度

3. 这些方法都不是什么秘密,稍微详细一些的数据结构课程里都有,所以有一点可以肯定,上课认真听讲是没有坏处的

4. 最后是一个好消息,2008 C++标准里已经有hash表了,只不过换了一个官方名称"unordered",有点怪哈

-----------
欢迎到我的博客 http://thejinchao.blog.sohu.com

Blizzard的MPQ文件格式搜索算法---来自Gameres bbs相关推荐

  1. 论坛首页调用 来自 http://bbs.apabi.com

    From http://bbs.apabi.com/dispbbs.asp?boardID=24&ID=17411&page=1 <?xml version="1.0& ...

  2. SCJP考题中的陷阱---来自水木清华BBS

    发信人: ifyr (元旦加措), 信区: Java         标  题: SCJP考题中的陷阱[from:chinajavaworld]  发信站: BBS 水木清华站 (Fri May 24 ...

  3. Blizzard经典之打造最快的Hash表

    大学时代,对于Blizzard情有独钟,特别是100M左右的程序,能够打造出来魔兽争霸,简直是叹为观止!一个能在386上运行流畅的游戏,仅仅用了256色的颜色索引表,就打造了其游戏中丰富的视觉感受,从 ...

  4. PE文件格式和ELF文件格式(上)----PE文件

    PE文件格式详解 作者:MSDN 译者:李马 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式.PE文件格式的规范包含在了MSDN的CD中(Specs and Strategy ...

  5. mmCIF 文件格式

    mmCIF 文件格式 转载来自:https://www.jianshu.com/p/73453d80de9e mmCIF数据文件 mmCIF数据文件和字典中使用的语法来自STAR(Self-defin ...

  6. 【破解教程】PE文件格式详解(上)

    PE文件格式详解(上) 摘要 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式.PE文件格式的规范包含在了MSDN的CD中(Specs and Strategy, Specif ...

  7. 一个游戏策划的八年回忆录

    一个游戏策划的八年回忆录 来自gameres:http://bbs.gameres.com/showthread.asp?threadid=170275 八年了,刚刚好八年-- 第一回:进智冠打暑期工 ...

  8. 全志A10/A20 Bootloader加载过程分析

    原文 : http://blog.csdn.net/allen6268198/article/details/12905425 从这里开始:http://linux-sunxi.org/Bootabl ...

  9. 一个简单的PE感染病毒

    /*----------2015-09-28 Update ----------*/ 将shellcode多线程执行 使用CreateThread函数开辟子线程 子线程会开辟新的堆栈,需要进行新的压栈 ...

最新文章

  1. 知乎高赞:我的编程能力从什么时候开始突飞猛进的?
  2. python资料库-Python 操作数据库之 records
  3. 从0到1 构建实时音视频引擎
  4. [leetcode]@python 85. Maximal Rectangle
  5. 重磅!全球Top 1000计算机科学家h指数公布:中国53位学者上榜!张宏江居大陆科学家之首...
  6. 数据产品-数据指标标签常用sql函数
  7. HTC Vive 叠影器无法创建设备
  8. python网页内容获取记录pkg
  9. python 保存scv文件乱码与报错的问题解决TypeError: a bytes-like object is required, not 'str'
  10. 破解完全入门篇 第七章-寻找软件的注册码
  11. 网易buff服务器不稳定,《梦幻西游》手游平民94方寸逆袭成神分享心得助你腾飞_ 《梦幻西游》手游官网-人人都玩,无处不在...
  12. android 模拟器的使用
  13. 详解如何在Sbo Add-on开发中使用Folder控件
  14. 千古绝唱——陆游和唐琬
  15. 暑期集训1期11暑期集训一期12阶段性测验
  16. mysql 5.6 安装库_MySQL5.6安装步骤图文详解
  17. JavaScript变量
  18. 卷积层TSNE可视化
  19. 蛮力法-分治法-处理最近对问题
  20. 算法C++ DepthFirstSearch BreadthFirstSearch代码模式示范实现(第四章)

热门文章

  1. 操作系统:存储器管理(下)
  2. element-ui文件上传修改上传文件的格式
  3. [LBS学习笔记4]地理特征POI、AOI、路径轨迹
  4. 【项目】小帽商城 II(一)
  5. jQ选择器与常用的方法归纳
  6. 【每日一题】把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5]
  7. apollo学习之---基础知识点学习(1)commen_math
  8. 如何防范恶意网站(转)
  9. 【苹果家庭推送iMessage】软件安装应用程序访问HealthKit HomeKit
  10. Android App赞赏功能,微信公众号赞赏功能升级:作者可以直接收到赞赏