揭秘| 帶你走進哈希表(Hash Table)的世界

2020-12-27 電子工程世界網


為什麼會有哈希表這種數據結構呢?讓我們用一個通俗的例子來理解:
大家一定都查過字典吧,我們知道,《新華字典》是按照讀音排序的,可以理解為一個以讀音為key,按升序排列的資料庫。對於讀音已知的字,可以通過「二分查找法」,很快地查找到要找的字,其時間複雜度為O(log2n)。


但是,對於不知道讀音的字怎麼辦呢?如果使用「順序查找」,一頁一頁地翻字典,假設一本新華字典600頁,每翻查一頁的時間開銷為0.5分鐘,那麼,每查到一個字耗費的時間t的數學期望值E(t) = 600 * 0.5min / 2 =150min,也就是查一個字需要兩個半小時!當然,這是難以接受的!


為了解決這個問題,《新華字典》的編輯們,很快就想出了解決辦法,那就是在字典的前面加入一個「檢字表」,如「四角號碼檢字表」「部首檢字表」等,其特點是以每個字的字形為依據,計算出一個索引值,並映射到對應的頁數。比如「法」字,按
四角號碼檢字法,其索引值為34131,再根據這個數值,就可以找到相應的字了。在這種情況下,查找算法的時間複雜度接近於O(1)。換句話說,字典再厚,也不會明顯地影響到查字典的效率了。

好,讓我們回到計算機的世界中來。


哈希表的最大特點,是數據存儲位置(偏移量)和數據記錄的內容相關,存在著一個函數換算關係:

Offset = Hash (Key)
其中,Offset為數據存儲的偏移量,Hash為散列函數(Hash Function),Content為數據記錄內容的關鍵字(Hash Key)。假設,我們要建立一張EEWorld全球訪問量統計表,每條記錄包含下面的數據結構:

struct access_record_t {
unsigned index_i; /* Index*/
charcountry_name[MAX_COUNTRY_NAME_LEN]; /* 國家/地區名 */
unsigned long long access_count;/* 訪問量 */
};

我們可以用一個一維數組access_record存儲這張表,其中access_record[index_i]為編號為index_i的國家的記錄,也就是說,數據的存儲位置由index_i值唯一確定。例如,中國大陸的index_i值為86,那麼,access_record[86].access_count即為EEWorld在中國大陸地區的訪問量。
然而,我們知道,「China」比起數字86來,明顯更接近自然語言,對於人腦的記憶來說更方便,所以,我們能不能想一個辦法,做一個從國家/地區名到數字索引的映射呢?這就涉及到散列函數(Hash Function)了。


我們說到了散列函數(Hash Function)。它又名哈希函數,是計算機科學中一個重要的課題。什麼是散列函數呢?其實,這個概念並沒有一個嚴格的定義。一般說來,散列函數滿足以下的條件:


1、對輸入值運算,得到一個固定長度的摘要(Hash value);

2、不同的輸入值可能對應同樣的輸出值;
以下的函數都可以認為是一個散列函數:


f(x) = x mod 16;    (1)

f(x) = (x2 + 10) * x;    (2)
f(x) = (x | 0×0000FFFF) XOR (x >> 16);    (3)

不過,僅僅滿足上面這兩條的函數,作為散列函數,還有不足的地方。我們還希望散列函數滿足下面幾點:


1、散列函數的輸出值儘量接近均勻分布;

2、x的微小變化可以使f(x)發生非常大的變化,即所謂「雪崩效應」(Avalanche effect);


上面兩點用數學語言表示,就是:


1,  輸出值y的分布函數F(y)=y/m, m為散列函數的最大值。或記為y~U[0, m]

2,|df(x)/dx| >> 1;


從上面兩點,大家看看,前面舉例的三個散列函數,哪個更好呢?對了,是第三個:

f(x) = (x | 0×0000FFFF) XOR (x >> 16);
它很完美地滿足「好的散列函數」的兩個附加條件。


那麼,為什麼散列函數要帶有這兩個附加條件呢?原來,這是為了減少「哈希衝突」(Hashcollision),也就是兩個不同輸入產生了相同輸出值的情況。根據抽屜原理,Hash算法不可能沒有衝突(collision),但是,由於衝突會造成一些問題,可能會影響到應用Hash函數的某些算法的效率,所以,我們需要儘量避免之。這樣,對Hash算法的選擇,就是很重要的了。密碼學中的著名摘要算法的
MD5SHA1,以及另一種用於字符串摘要計算的Jenkins Hash算法,都是很經典的Hash算法,有興趣的同學可以閱讀參考。


順便延伸閱讀一下,對計算機信息安全感興趣的同學,一定聽說過密碼學家
王小雲教授。王教授成名的貢獻,就是發現了大大加速找出MD5和SHA1等Hash算法衝突的方法。譬如,根據「生日攻擊」理論,對於Hash value為160bit的SHA1算法,找出一個Hash衝突需要280次運算,而王小雲找出了一個269次運算就能找出衝突的算法,也就是提高了211=2048倍的效率!所以說,王教授的成果大大動搖了現代密碼學的基礎。


Hash表中,數據存儲的位置,是通過Hash函數計算得到的。那麼,如果兩條數據記錄的Hash值發生衝突,應該怎麼辦呢?


在Hash表的建立時,會發生Hash值衝突的情況。實際上,如果記錄Hash值的範圍多於Hash表的條數,根據抽屜原理,一定會發生衝突。對於衝突的處理,我們一般有這幾種方法:


1,對Hash表中每個Hash值建立一個衝突表,即將衝突的幾個記錄以表的形式存儲在其中;
2,改變規則重新計算一次Hash值;
3,建立一個公用的區域存放衝突的表項;


在工程上,考慮到實現算法的複雜度,方法1用得是最多的。對於方法1,又有兩種不同的實現,一種方法是對每個Hash值,建立一個Hash桶(Bucket),桶的容量是固定的,也就是只能處理固定次數的衝突,如1048576個Hash桶,每個桶中有4個表項(Entry),總計4M個表項。另一種方法是,不限制Hash桶的容量,以鍊表形式將衝突的記錄掛接在一個Hash桶中。

這兩種實現各有什麼利弊呢?首先,讓我們看看第一種實現:在這種情況下,由於Hash桶容量的限制,所以,有可能發生Hash表填不滿的情況,也就是,雖然Hash表裡面還有空位,但是新建的表項由於衝突過多,而不能裝入Hash表中。不過,這樣的實現也有其好處,就是查表的最大開銷是可以確定的,因為最多處理的衝突數是確定的,所以算法的時間複雜度為O(1)+O(m),其中m為Hash桶容量。


而另一種實現,由於Hash桶的容量是無限的,因此,只要沒有超出Hash表的最大容量,就能夠容納新建的表項。但是,一旦發生了Hash衝突嚴重的情況,就會造成Hash桶的鍊表過長,大大降低查找效率。在最壞的情況下,時間複雜度退化為O(n),其中n為Hash表的總容量。當然,這種情況的概率小之又小,幾乎是可以忽略的。

Hash表的一個應用例子,是在網關(Gateway)中。以網絡防火牆為例,它是根據源IP,目的IP,源埠,目的埠,協議號構成的五元組來標識一條網絡數據流的,並且根據五元組來建立會話表項(session entry)。為了查找便捷,一般都使用Hash表來實現這個會話表,以提高轉發的效率。事實上,對於大量表項的查找,逐項查找是不允許的,一般都使用Hash表來實現。不誇張的說,我們可以說,在你生活的每一天,都免不了同Hash表打交道,比如,查字典。

最後,我想說,數據結構的萬紫千紅中,我獨愛Hash表這一種。


相關焦點

  • 數據結構-PHP 哈希表(Hash Table)的實現
    這個映射函數叫做哈希函數,存放記錄的數組叫哈希表(Hash Table)。1.哈希表(Hash Table)的特點訪問速度很快,將指定的 Key 都映射到一個地址上,在訪問時,可以直接跳到那個地址。空間換時間,哈希表充分體現了空間換時間的思想。無序,哈希表是無序的。它為了能快速訪問元素,值是根據哈希函數直接找到存儲地址的。可能會產生哈希衝突,不同的值通過哈希函數可能存在相同值的情況,這時就需要採用衝突解決方案。
  • 揭秘 | 帶你走進哈希表(Hash Table)的世界
    為什麼會有哈希表這種數據結構呢?讓我們用一個通俗的例子來理解:大家一定都查過字典吧,我們知道,《新華字典》是按照讀音排序的,可以理解為一個以讀音為key,按升序排列的資料庫。對於讀音已知的字,可以通過「二分查找法」,很快地查找到要找的字,其時間複雜度為O(log2n)。但是,對於不知道讀音的字怎麼辦呢?
  • 談談 Hash Table
    鍊表的單個節點一般為結構體或者對象,因為鍊表的單個節點除了需要保存數據之外還需要維護它的相鄰節點的關係,如果想獲得鍊表中的某個節點的值,需要從鍊表的頭結點開始遍歷,直到找到需要的東西,而插入或者刪除某個節點的話,需要找到相應的節點,修改其以及其相鄰節點的相關指針的引用即可。像其他的數據結構,比如 隊列,棧,樹,都可以通過數組或者鍊表來組織,並實現相應的操作功能。
  • 深入理解哈希表
    雖然負載因子會降低,但實際存儲在每個箱子中的鍊表長度並不發生改變,因此也就不能提高哈希表的查詢性能。基於以上總結,細心的讀者可能會發現哈希表的兩個問題:如果哈希表中本來箱子就比較多,擴容時需要重新哈希並移動數據,性能影響較大。如果哈希函數設計不合理,哈希表在極端情況下會變成線性表,性能極低。
  • 淺談「Hash table」
    每天早上六點半,我們不見不散。哈希表(Hash Table)也叫散列表,是根據關鍵碼值(Key Value)而直接進行訪問的數據結構。它通過把關鍵碼值映射到哈希表中的一個位置來訪問記錄,以加快查找的速度。這個映射函數就做散列函數,存放記錄的數組叫做散列表。
  • 哈希函數和哈希表
    但是,看完今天的文章,你或許就會覺得原來也不過如此啊!其核心就是哈希函數和哈希表的應用!哈希函數哈希函數又稱為散列函數,就是把任意長度的輸入(又叫做預映射, pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。
  • 【譯】Oracle調優技巧22:Hash Outer Join
    原文:Hash Outer Join : Oracle Tuning Tip#21作者:kali話題Hash Outer Join(散列外連接,音譯哈希連接)定義根據Hash Outer Join 的定義,保留驅動表(父表)的行信息用於構建哈希表( hash table),被驅動表(子表)用於探測哈希表。
  • Python | Leetcode哈希表系列
    哈希表哈希表(散列表)的思想是將關鍵字 Key 映射到存放記錄的散列表中從而進行快速訪問,其中映射函數 f(key) 稱為哈希函數(散列函數),依據哈希函數建立的查找表稱為哈希表。假如班級裡面有50個學生,都有各自的名字,我們想把學生的名字放在一個表上,同時便於很快的查找,你會毫無想到數組,但是查找的複雜度是O(n),所以哈希表就是為了解決這問題,將查找的複雜度達到 O(1)假如有個學生的名字叫做lies,通過hash函數,得到下標9 ,將lies的ASCII碼加起來
  • 圖解:什麼是哈希?
    由於哈希函數的原理是將輸入空間的一個較大的值映射成 hash 空間內一個較小的值,那麼就會出現兩個不同的輸入值被映射到了同一個較小的輸出值。當一個新插入的值被哈希函數映射到了哈希表中一個已經被佔用的槽,就認為產生了 Hash 碰撞(衝突)。那麼這種衝突是否可以避免呢?
  • 哈希(Hash)和哈希樹(Merkletree)
    哈希函數(英語:Hash function)又稱散列函數,是一種從任何一種數據中創建小的數字「指紋」的方法。散列函數把消息或數據壓縮成摘要,使得數據量變小,將數據的格式固定下來。
  • 理解 Golang 哈希表 Map 的原理
    ;B 表示了當前哈希表持有的 buckets 數量,但是因為哈希表的擴容是以 2 倍數進行的,所以這裡會使用對數來存儲,我們可以簡單理解成 len(buckets) == 2^B;hash0 是哈希的種子,這個值會在調用哈希函數的時候作為參數傳進去,它的主要作用就是為哈希函數的結果引入一定的隨機性;oldbuckets 是哈希在擴容時用於保存之前 buckets
  • PHP哈希表碰撞攻擊原理
    哈希表碰撞攻擊的基本原理哈希表是一種查找效率極高的數據結構,很多語言都在內部實現了哈希表。PHP中的哈希表是一種極為重要的數據結構,不但用於表示Array數據類型,還在Zend虛擬機內部用於存儲上下文環境信息(執行上下文的變量及函數均使用哈希表結構存儲)。
  • 數據結構 | 哈希表
    本文為數據結構系列第 11 篇,介紹哈希表的實現。哈希表(也叫散列表)是數據結構課程中都會介紹的概念,一般出現在「散列與查找」相關章節。哈希表的核心思想是構造一個散列函數,鍵值作為散列函數輸入,將元素映射到哈希表某一位置存儲。這樣,在進行查找時就可以根據散列函數快速找到元素所在位置。常見散列函數有:直接定址法、平方取中法、除留餘數法、數字分析法等。
  • 哈希表的原理,真的很難弄懂麼?
    HashSet的底層數據結構:哈希表。前天學習了Collection集合,其繼承體系圖如下:今天就來了解Collection的子接口List,Set,以及它們各自的實現類。所以什麼叫hashCode?hashCode是對真正的地址進行一種加密手段而得到的一串數字(什麼手段也不用去了解,除非你要去做黑客)。那麼現在問題來了,有沒有可能存在多個對象地址,對應同一個hashCode呢?
  • 圖解MySQL | [原理解析] Adaptive Hash Index 是如何建立的
    Adaptive Hash Index(以下簡稱 AHI)估計是 MySQL 的各大特性中,大家都知道名字但最說不清原理的一個特性。
  • 人們常說的哈希(Hash)到底是什麼?
    了解過區塊鏈的朋友,一定聽過「哈希」這一詞彙,然而對哈希的概念又極其的模糊,那麼哈希是什麼呢?Hash一般被翻譯成「散列」,也可直接音譯為「哈希」,就是把任意長度的輸入(又叫做預映射, pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。
  • Hash(哈希)密碼破解原理
    但下載比生成快得多,有人做過測試,4核4GB內存的機器,生成2GB彩虹表,需要花費7天時間,而7天按2MB的帶寬(256K/S左右)幾乎可以下載48GB左右,效率明顯要超過生成。當然,你要是有超級計算機群生成的話,也不妨自己生成。對於廣大網絡安全愛好者來說,還是直接下載來得靠譜!
  • 常見 Hash 算法的原理
    散列表(Hash table,也叫哈希表),是依據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。比方我們存儲70個元素,但我們可能為這70個元素申請了100個元素的空間。70/100=0.7,這個數字稱為負載因子。
  • 五分鐘帶你了解哈希算法
    確定性不僅對哈希很重要,而且可以改變輸入的單個字符會產生完全不同的哈希。哈希算法的問題是碰撞(collisions)的必然性。哈希是固定的字符串,意味著對於每個輸入,不同的輸入都會產生同樣的輸出。碰撞(collisions)是不好的。這意味著如果有攻擊者能夠根據需求創建這種collisions,那麼他就可以讓欺詐文件或者數據看起來像正確的,合適的哈希,並且冒充合法。
  • 圖解什麼是一致性哈希算法
    3.分布式系統和一致性哈希要理解一致性哈希算法就需要知道分布式系統的一些特點。年輕人你肯定會問:為啥呢?實際中對於規模較小的系統來說,哈希取模策略應用很廣泛,接下來重點介紹hash取模和一致性哈希的區別與聯繫。4.