原文来自: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。

转载于:https://www.cnblogs.com/learn-my-life/p/4708127.html

暴雪的hash算法[翻译]相关推荐

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

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

  2. 用人话理解hash算法、特性、唯一性等

    背景 博主在本科的时候曾经参加过数学建模,当初提交参赛论文的时候有两个提交时间点,第一个是提交MD5码的时间,第二个是提交具体参赛论文的时间,当初就很纳闷,直接提交论文不就ok吗?为什么多此一举呢 我 ...

  3. hash的算法 java_【数据结构与算法】一致性Hash算法及Java实践

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

  4. python dict hash算法_2020年3月26日python学习笔记——hash

    什么是哈希? hash,一般翻译做散列.杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射,也就是,散列 ...

  5. Hash算法中的CRC

    CRC是通信领域中用于校验数据传输正确性的最常用机制,也是Hash算法的一个典型应用,Hash一般翻译为"散列",也可直接音译为"哈希",就是把任意长度的输入( ...

  6. 关于一致性hash算法的几个问题

    本文来说下关于一致性hash算法的几个问题 文章目录 分布式系统的基本概念 分布式系统与高并发高可用 集群和分布式的区别与联系 分布式系统的分发 常用负载均衡策略 Hash取模策略 Hash取模的弊端 ...

  7. 布谷鸟哈希函数的参数_系统学习hash算法(哈希算法)

    系统学习hash算法(哈希算法) 转载请说明出处. 前言: 关于本文<系统学习hash算法>的由来.在看到了<十一.从头到尾彻底解析Hash 表算法>这篇文章之后,原文中没有暴 ...

  8. 彻底了解HASH算法及应用(一)

    2019独角兽企业重金招聘Python工程师标准>>> 转载地址:HASH算法:http://blog.csdn.net/v_JULY_v/article/details/62564 ...

  9. hash算法和常见的hash函数

    Hash,一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre--mage),通过散列算法,变换成固定长度的输出,该输出就 ...

最新文章

  1. parameter与define 区别
  2. 我的Go语言学习之旅五:Go循环输出的另类实现
  3. AutoHomeRefreshListView仿汽车之家下拉刷新 《IT蓝豹》
  4. 30个提高Web程序执行效率的好经验
  5. C#操作Win7/Win8的库、收藏夹
  6. 知识分享|日本面试常考问题+巧妙回答 ②
  7. 两球完全弹性碰撞反弹方向问题
  8. Nacos 2.0原理解析(一):Distro协议
  9. 【Java编程】写一个将华氏温度转换成摄氏温度的程序,转换的公式是:°F = (9/5)*°C + 32 其中C表示摄氏温度,F表示华氏温度。
  10. MapReduce编程快速入门
  11. dns 性能测试 dnsperf
  12. Python面向对象版学员管理系统
  13. 星辰变java镇东王府在哪里_星辰变中让人非常失望的四位高手,最后一位只动口不出手!...
  14. 扒皮下音悦台的“返回顶部”图标效果
  15. 剑网3官方(内置)宏语法说明---(转自令狐志超帖子)
  16. EOS的中心化,该中国财团背锅吗?
  17. 访问Servlet的几种方式
  18. Java网页编程与设计——javascript语言
  19. 工作交接_java后端
  20. 微软图形控件(Chart或MSchart)的用法

热门文章

  1. Weblogic IIOP协议反序列化(CVE-2020-2551)漏洞复现
  2. 基于API激光跟踪仪测量的具有平行关节(考虑平行度误差)的串联臂MDH运动学参数标定
  3. Compile IceE under Android NDK
  4. mysql工作笔记_mysql 工作笔记
  5. 从数据库中查询日历SQL
  6. 超大规模视觉通用感知模型
  7. js设计模式之享元模式
  8. 【Proteus仿真】2片CD4026联级秒脉冲2位数码管计数
  9. 腾讯云主机SSL证书配置
  10. 【愚公系列】2023年04月 Halcon机器视觉-基于Halcon车牌识别