暴雪公司的魔獸、星際等遊戲都一樣乙個非常大的mpq檔案,該檔案儲存了遊戲中的大部分資料,想要把這些文字找出來,簡單的辦法是從陣列頭開始,乙個個字串讀過去,比較每乙個,直到找到對應的內容。blizzard的天才和牛人們當然不會這樣做,他們用了更聰明的方法: 用某種演算法,把乙個字串壓縮成乙個整數,即hash。然後,根據這個整數值,直接得到此字串在整個檔案中的位置,從而直接讀取之。
blizzard的這個演算法是非常高效的,被稱為」one-way hash」。所謂one-way hash,就是無法從求得的hash值通過簡單的逆運算就得到原來的字串。關於具體的實現原理,inside mpq 的第二章有詳細的介紹,以下為第二章內容的翻譯:
貫穿計算機發展歷史,大多數進步都是源於某些問題的解決,在這一節中,我們來看一看與mpq 格式相關問題及解決方案;
問題一:你有乙個很大的字串陣列,同時,你另外還有乙個字串,需要知道這個字串是否 已經存在於字串陣列中。你可能會對陣列中的每乙個字串進行比較,但是在實際專案中,你會發現這種做法對某些特殊應用來說太慢了。必須尋求其他途徑。那麼如何才能在不作遍歷比較的情況下知道這個字串是否存在於陣列中呢?
解決方案:雜湊表。雜湊表是通過更小的資料型別表示其他更大的資料型別。在這種情況下, 你可以把雜湊表儲存在字串陣列中,然後你可以計算字串的雜湊值,然後與已經儲存的字串的雜湊值進行比較。如果有匹配的雜湊值,就可以通過字串比較 進行匹配驗證。這種方法叫索引,根據陣列的大小以及字串的平均長度可以約100倍。 1
2
3
4
5
6
7
8
9
10
unsigned
long
hashstring
(char
*lpszstring)
return
ulhash;
}上面**中的函式演示了一種非常簡單的雜湊演算法。這個函式在遍歷字串過程中,將雜湊值左移一位,然後加上字元值;通過這個演算法,字串」arrunits.dat」 的雜湊值是0x5a858026,字串」unitneutralacritter.grp」 的雜湊值是0x694cd020;現在,眾所周知的,這是乙個基本沒有什麼實用價值的簡單演算法,因為它會在較低的資料範圍內產生相對可**的輸出,從而可能會產生大量衝突(不同的字串產生相同的雜湊值)。
mpq格式,使用了一種非常複雜的雜湊演算法(如下所示),產生完全不可**的雜湊值,這個演算法十分有效,這就是所謂的單向雜湊演算法。通過單向雜湊演算法幾乎不可能通過雜湊值來唯一的確定輸入值。使用這種演算法,檔名 「arrunits.dat」 的雜湊值是0xf4e6c69d,」unitneutralacritter.grp」 的雜湊值是 0xa26067f3。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned
long
hashstring
(char
*lpszfilename
,unsigned
long
dwhashtype)
return
seed1;
}問題二:您嘗試在前面的示例中使用相同索引,您的程式一定會有中斷現象發生,而且不夠快 。如果想讓它更快,您能做的只有讓程式不去查詢陣列中的所有雜湊值。或者 您可以只做一次對比就可以得出在列表中是否存在字串。聽起來不錯,真的麼?不可能的啦
解決:乙個雜湊表就是以字串的雜湊值作為下標的一類陣列。我的意思是,雜湊表使用乙個固定長度的字串陣列(比如1024,2的偶次冪)進行儲存;當你要看看這個字串是否存在於雜湊表中,為了獲取這個字串在雜湊表中的位置,你首先計算字串的雜湊值,然後雜湊表的長度取模。這樣如果你像上一節那樣使用簡單的雜湊演算法,字串」arrunits.dat」 的雜湊值是0x5a858026,偏移量0x26(0x5a858026 除於0x400等於0x16a160,模0x400等於0x26)。因此,這個位置的字串將與新加入的字串進行比較。如果0x26處的字串不匹配或不存在,那麼表示新增的字串在陣列中不存在。下面是示意的**: 1
2
3
4
5
6
7
8
intgethashtablepos
(char
*lpszstring
,somestructure *
lptable
,int
ntablesize)
上面的說明中存在乙個刺眼的缺陷。當有衝突(兩個不同的字串有相同的雜湊值)發生的時候怎麼辦?顯而易見的,它們不能佔據雜湊表中的同乙個位置。通常的解決辦法是為每乙個雜湊值指向乙個鍊錶,用於存放所有雜湊衝突的值;
mpqs使用乙個存放檔名的雜湊表來跟蹤檔案內部,但是表的格式與通常方法有點不同,首先不像通常的做法使用雜湊值作為偏移量,儲存實際的檔名。mpqs 根本不儲存檔名,而是使用了三個不同的雜湊值:乙個用做雜湊表偏移量,兩個用作核對。這兩個核對的雜湊值用於替代檔名。當然從理論上說存在兩個不同的檔名得到相同的三個雜湊值,但是這種情況傳送的機率是:1:18889465931478580854784,這應該足夠安全了。
mpq』s的雜湊表的實現與傳統實現的另乙個不同的地方是,相對與傳統做法(為每個節點使用乙個鍊錶,當衝突發生的時候,遍歷鍊錶進行比較),看一下下面的示範**,在mpq中定位乙個檔案進行讀操作: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
intgethashtablepos
(char
*lpszstring
,mpqhashtable *
lptable
,int
ntablesize)
return-1
;//error value
} 無論**看上去有多麼複雜,其背後的理論並不難。讀乙個檔案的時候基本遵循下面這樣乙個過程:
1. 計算出字串的三個雜湊值(乙個用來確定位置,另外兩個用來校驗)
2. 察看雜湊表中的這個位置
3. 雜湊表中這個位置為空嗎?如果為空,則肯定該字串不存在,返回
4. 如果存在,則檢查其他兩個雜湊值是否也匹配,如果匹配,則表示找到了該字串,返回
5. 移到下乙個位置,如果已經越界,則表示沒有找到,返回
6. 看看是不是又回到了原來的位置,如果是,則返回沒找到
7. 回到3
如果您注意的話,您可能已經從我們的解釋和示例**注意到,mpq的雜湊表已經將所有的檔案入口放入mpq中;那麼當雜湊表的每個項都被填充的時候,會發生什麼呢?答案可能會讓你驚訝:你不能新增任何檔案。有些人可能會問我為什麼檔案數量上有這樣的限制(檔案限制),是否有辦法繞過這個限制?就此而言,如果不重新建立mpq 的項,甚至無法調整雜湊表的大小。這是因為每個項在雜湊表中的位置會因為跳閘尺寸而改變,而我們無法得到新的位置,因為這些位置值是檔名的雜湊值,而我們根本不知道檔名是什麼。
暴雪的hash演算法 翻譯
促進歷史進步的大多數契機都是在解決特定問題的過程中產生的,本文討論一下mpq格式的合適解決方案。mpq是暴雪的一種文字壓縮格式,可以壓縮包括座標 演算法 聲音 動畫 字串等。hashs 問題 你可能有乙個非常長的字串陣列,現在有乙個新字串,想要判斷該字串是否在陣列中,簡單粗暴的方法是挨個比較,但最大...
神奇的Perl 雜湊和陣列 6
1.1.1 each values keys sort 1 each each一般作用於雜湊和陣列,以2元素的列表形式返回雜湊的鍵值對和陣列的索引 值對。語法如下 each hash each array 比如 while key value each hash while index,value ...
演算法筆記 神奇的口袋
有乙個神奇的口袋,總的容積是40,用這個口袋可以變出一些物品,這些物品的總體積必須是40。john現在有n個想要得到的物品,每個物品的體積分別是a1,a2 an。john可以從這些物品中選擇一些,如果選出的物體的總體積是40,那麼利用這個神奇的口袋,john就可以得到這些物品。現在的問題是,john...