神奇的暴雪哈希算法( 二 )

上面的说明中存在一个刺眼的缺陷 。当有冲突(两个不同的字符串有相同的哈希值)发生的时候怎么办?显而易见的,它们不能占据哈希表中的同一个位置 。通常的解决办法是为每一个哈希值指向一个链表,用于存放所有哈希冲突的值;
MPQs使用一个存放文件名的哈希表来跟踪文件内部,但是表的格式与通常方法有点不同,首先不像通常的做法使用哈希值作为偏移量,存储实际的文件名 。MPQs 根本不存储文件名,而是使用了三个不同的哈希值:一个用做哈希表偏移量,两个用作核对 。这两个核对的哈希值用于替代文件名 。当然从理论上说存在两个不同的文件名得到相同的三个哈希值,但是这种情况发送的几率是:1:18889465931478580854784,这应该足够安全了 。
MPQ’s的哈希表的实现与传统实现的另一个不同的地方是,相对与传统做法(为每个节点使用一个链表,当冲突发生的时候,遍历链表进行比较),看一下下面的示范代码,在MPQ中定位一个文件进行读操作:
01 . int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize)02 . {03 . const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;04 . int nHash = HashString(lpszString, HASH_OFFSET);05 . int nHashA = HashString(lpszString, HASH_A);06 . int nHashB = HashString(lpszString, HASH_B);07 . int nHashStart = nHash % nTableSize,nHashPos = nHashStart;08 . while (lpTable[nHashPos].bExists)09 . {10 . if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB)11 . return nHashPos;12 . else13 . nHashPos = (nHashPos + 1) % nTableSize;14 . if (nHashPos == nHashStart)15 . break;16 . }17 . return -1; //Error value18. }无论代码看上去有多么复杂,其背后的理论并不难 。读一个文件的时候基本遵循下面这样一个过程:

  • 1. 计算出字符串的三个哈希值(一个用来确定位置,另外两个用来校验)
  • 2. 察看哈希表中的这个位置
  • 3. 哈希表中这个位置为空吗?如果为空,则肯定该字符串不存在,返回
  • 4. 如果存在,则检查其他两个哈希值是否也匹配,如果匹配,则表示找到了该字符串,返回
  • 5. 移到下一个位置,如果已经越界,则表示没有找到,返回
  • 6. 看看是不是又回到了原来的位置,如果是,则返回没找到
  • 7. 回到3
如果您注意的话,您可能已经从我们的解释和示例代码注意到,MPQ的哈希表已经将所有的文件入口放入MPQ中;那么当哈希表的每个项都被填充的时候,会发生什么呢?答案可能会让你惊讶:你不能添加任何文件 。有些人可能会问我为什么文件数量上有这样的限制(文件限制),是否有办法绕过这个限制?就此而言,如果不重新创建MPQ 的项,甚至无法调整哈希表的大小 。这是因为每个项在哈希表中的位置会因为跳闸尺寸而改变,而我们无法得到新的位置,因为这些位置值是文件名的哈希值,而我们根本不知道文件名是什么 。




推荐阅读