原文来自:http://sfsrealm.hopto.org/inside_mopaq/chapter2.htm#hashes

促进历史进步的大多数契机都是在解决特定问题的过程中产生的,本文讨论一下MPQ格式的合适解决方案。

----MPQ是暴雪的一种文本压缩格式,可以压缩包括坐标、算法、声音、动画、字符串等。

HASHS

问题:你可能有一个非常长的字符串数组,现在有一个新字符串,想要判断该字符串是否在数组中,简单粗暴的方法是挨个比较,但最大的弊端就是大部分场合下速度慢到不能忍。

解决方案:hash,hash是一种较小的数据类型,用来替代其他的较大的数据类型(通常是字符串),上述的问题可以存储字符串的hash到数组中,然后比较新字符串的hash是否在数组中,如果数组中的一个hash匹配了新字符串的hash,该字符串就存在于字符串数组中。hash能够将该比较过程提速约100倍,具体的效率提升依赖于数组的长度和字符串的平均长度,下面是一个简单的hash算法

unsigned long HashString(char *lpszString)
{
unsigned long ulHash = 0xf1e2d3c4;

while (*lpszString != 0)
{

ulHash <<= 1;
ulHash += *lpszString++;
}

return ulHash;

}

上面的代码提供了一种简单的hash算法,累加字符串的所有字符,在累加字符串之前hash的基准值左移一位。这个算法简单也很有用,但会生成大量相似的输出,并且经常是在较小的值域内产生大量冲突,冲突是指两个不同的字符串hash后值一样。

MPQ格式使用了非常复杂的hash算法来生成全部不可预期的值,事实上该hash算法是一个单向算法,单向算法是指无法通过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;
}

hash表

问题:你使用一个类似上面例子的索引,但你的程序要求极快的速度,但是索引并没有足够快,你唯一能让索引速度变快的方法是不检索数组中的每一个hash值。更或者你可以只做一次比较就能确定字符串是否存在于数组的任何一个位置,听起来爽吧?

解决方案:hash表是一种特殊的数组,目标字符串的偏移量是目标字符串的hash值。根据应用程序需要创建指定大小的数组,(比如说1024,通常是2的n次方),想要确认新字符串是否在表里,为了获取新字符串在hash表的位置,将新字符串的hash按照hash表大小进行取模运算,余数就是新字符串在hash表的偏移量。然后将hash表指定偏移位置的字符串同新增字符串进行比较,如果不存在或者不相等,那么该字符串没有存在于hash表也即字符串数组中。代码如下:

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
}

上面的方法有一个明显的问题,如果发生了冲突即两个不同的字符串hash后的值是一样的,又该怎么办?显然它们不能在hash表占用同样的位置。一个常用的解决方式是hash的每一个节点不再代表一个元素,而是一个列表的指针,该列表保存hash值为该偏移的所有字符串。

MPQs使用一个文件名称表来跟踪文件内部信息,但这个表和普通的hash表并不完全一样。首先:不是使用文件名称的hash值作为偏移量并存储真实的文件名来做校验;MPQs根本不会存储文件名称,而是使用三个不同的hash值:一个用来做hash表的偏移位置,两个用来做校验,两个用来校验的hash代替了真实的文件名称。这样还是有可能会出现两个不同的字符串hash到同样的三个值上,不过这样的概率非常小,大概是1:18889465931478580854784,谁识数的数一数,我是看不懂了,这样的概率对每一个人来说都是足够安全的。

另外MPQs和通常的hash实现不一样的地方是:没有为hash表的每一个入口保存一个链表,在冲突发生的时候继续向下找,直到找到一个未被占用的槽。

下面的代码描述了MPQs定位一个用来读的文件的过程

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), 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 + 1) % nTableSize;
 
if (nHashPos == nHashStart)
break;
}

return -1; //Error value

}

下面是对代码的解释,大致符合程序查找并读取文件的过程。

1.计算三个hash值并保存在变量里。

2.移动到hash偏移量的入口。

3.该入口是否未使用,如果是,停止搜索,返回查找失败。

4.是否入口的两个检验hash等于计算出来的两个检验hash,如果是,停止搜索并返回该入口。

5.移动到链表的下一个入口,如果到了末尾就重新从开始查找。

6.如果查找的偏移量和计算的hash偏移量是一样的,已经搜索了整个表仍然没有找到,返回未查找到。

7.从第三步重新开始。

如果留心的话,可能会注意到该表是不可扩展的,如果所有的入口都被占据,那就不能够插入任何的文件名称,是的,这个表就是这样设计的,装载因子最大为1.0.甚至该表不可以动态扩大,因为扩大会导致所有的hash入口失效,并且不能重新生成该表,因为不知道相应的文件名称。

-----如果只是为了一个传奇的hash,这里就应该够了,hash的原理、hash的应用都有足够的说明。

压缩

问题:如果你有一个巨大的程序,比如50m,想要通过网络传输,但这是一个非常巨大的下载量,人们也可能没有足够的耐心来等待它下载完毕。

解决方案:压缩。压缩是将大量数据存储在较小空间的技术,差不多有上百种压缩算法,工作方式各不相同。MPQs使用的算法是数据压缩lib的方式,太过复杂不做介绍,会另开文章介绍。

本文的后半部分和主题关系不大,分篇章翻译,over。

暴雪的hash算法[翻译]的更多相关文章

  1. Hash冲突的解决--暴雪的Hash算法

    Hash冲突的解决--暴雪的Hash算法https://usench.iteye.com/blog/2199399https://www.bbsmax.com/A/kPzOO7a8zx/

  2. 暴雪HASH算法(转)

    暴雪公司有个经典的字符串的hash公式 先提一个简单的问题,假如有一个庞大的字符串数组,然后给你一个单独的字符串,让你从这个数组中查找是否有这个字符串并找到它,你会怎么做? 有一个方法最简单,老老实实 ...

  3. hash算法总结收集

    hash算法的意义在于提供了一种快速存取数据的方法,它用一种算法建立键值与真实值之间的对应关系,(每一个真实值只能有一个键值,但是一个键值可以对应多个真实值),这样可以快速在数组等条件中里面存取数据. ...

  4. 【学】常用hash算法的介绍

    基本知识 Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映 ...

  5. hash算法

    作者:July.wuliming.pkuoliver 说明:本文分为三部分内容, 第一部分为一道百度面试题Top K算法的详解:第二部分为关于Hash表算法的详细阐述:第三部分为打造一个最快的Hash ...

  6. Hash算法初见

    hash算法 (hashmap 实现原理)   Hash ,一般翻译做“ 散列” ,也有直接音译为“ 哈希” 的,就是把任意长度的输入(又叫做预映射, pre-image ),通过散列算法,变换成固定 ...

  7. 【整理】hash算法原理及常见函数

    简介 Hash,一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值.        散列表 ...

  8. [速成]了解一致性hash算法

    定义 一致性hash算法,在维基百科的定义是: Consistent hashing is a special kind of hashing such that when a hash table ...

  9. 【数据结构与算法】一致性Hash算法及Java实践

    追求极致才能突破极限 一.案例背景 1.1 系统简介 首先看一下系统架构,方便解释: 页面给用户展示的功能就是,可以查看任何一台机器的某些属性(以下简称系统信息). 消息流程是,页面发起请求查看指定机 ...

随机推荐

  1. Python实现ATM+购物商城

    需求: 模拟实现一个ATM + 购物商城程序 额度 15000或自定义 实现购物商城,买东西加入 购物车,调用信用卡接口结账 可以提现,手续费5% 每月22号出账单,每月10号为还款日,过期未还,按欠 ...

  2. 移动端自动化测试-WTF Appium

    手机App分为两大类,原生App(Native App)和混合APP(Hybrid App) 原生App(Native App) 原生App实际就是我们所常见的传统App开发模式,云端数据存储+App ...

  3. 自编码器----Autoencoder

    一.自编码器:降维[无监督学习] PCA简介:[线性]原矩阵乘以过渡矩阵W得到新的矩阵,原矩阵和新矩阵是同样的东西,只是通过W换基. 自编码: 自动编码器是一种无监督的神经网络模型,它可以学习到输入数 ...

  4. NOIp2016-NOIp2011解题报告(骗分)

    zxl钦点.让我练暴力骗分. 那就把2016-2011年的题目搞一搞. NOIp2016 Day1 T1 AC 100pts. (妈呀,这么水的一道题竟然还要调试,一遍过不了样例,果然是要退役的节奏啊 ...

  5. java并发之停止线程

    停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作.停止一个线程可以用Thread.stop()方法,但最好不要用它.虽然它确实可以停止一个正在运行的线程,但是这个方法是不安 ...

  6. orcale 日期显示格式化

    SQL> select * 2 from emp 3 where hiredate='1987-11-17'; where hiredate='1987-11-17' * 第 3 行出现错误: ...

  7. ACDream - Graphs

    先上题目: Graphs Time Limit: 4000/2000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) Submit ...

  8. asp.net--owin的知识点

    一篇很好的介绍owin的文章 http://kb.cnblogs.com/page/509236/ Owin在webconfig中定义启动配置类IdentityConfig的方法(代码来自极客学院的教 ...

  9. Binding Enum to ComboBox

    1.添加MarkupExtension public class EnumToSourceExtension : MarkupExtension { private Type _type; publi ...

  10. oc23--变量修饰符

    // // Person.h #import <Foundation/Foundation.h> /* @public:所有类访问 @private:本类访问 @protected:本类子 ...