導讀 | 哈希表(Hashtable)又稱爲「散列」,Hashtable是會根據索引鍵的哈希程序代碼組織成的索引鍵(Key)和值(Value)配對的集合。Hashtable 對象是由包含集合中元素的哈希桶(Bucket)所組成的。而Bucket是Hashtable內元素的虛擬子羣組,可讓大部分集合中的搜尋和獲取工做更容易、更快速。 |
哈希函數(Hash Function)爲根據索引鍵來返回數值哈希程序代碼的算法。索引鍵(Key)是被存儲對象的某些屬性值(Value)。當對象加入至 Hashtable時,它存儲在與對象哈希程序代碼相符的哈希程序代碼相關的Bucket中。當在Hashtable內搜尋值時,哈希程序代碼會爲該值產生,而且會搜尋與該哈希程序代碼相關的Bucket。例如,student和teacher會放在不一樣的Bucket中,而dog和god會放在相同的 Bucket中。因此當索引鍵是惟一從Hashtable獲取元素的性能時表現會較好。Hash的四大優勢以下所示。html
· 事先不須要排序。linux
· 搜尋速度與數據多少無關。算法
· 數字簽名的密碼技術保密性(Security)高。編程
· 可作數據壓縮(Data Compression),以節省空間。安全
Linux內核裏的哈希表應用很是普遍,PHP內核裏大部分語言特性也是基於哈希表實現的。爲何哈希表能這麼神通廣大?哈希表可以實現高效的數據存儲和查找,而存儲和查找是編程中應用最普遍的兩個操做。數據結構
Linux內核裏的哈希表函數
讀過Linux內核源碼的人可能都會發現,其中並無太多複雜的數據結構,做爲基礎數據結構的雙向鏈表(list)和基於list實現的hash表佔據了絕大部分數據結構。內核爲何會大量使用這兩種數據結構呢?
首先,這兩種數據結構都十分簡單,簡單包括理解起來簡單和使用起來簡單兩方面內容。這也意味着代碼的可讀性和可維護性都比其餘複雜的數據結構要好,出現bug的風險也較低。從哲學上來說,這也符合K.I.S.S.條款。性能
其次,內核是一個比較講究性能的軟件,爲了程序設計和維護的簡單性而失掉性能,這到底是不是算得不償失呢?咱們是否是應該將天平更加偏向於性能?已經記不起是在哪裏據說過,不少商業的路由軟件都是基於二叉樹的數據結構來存儲路由項,以求得其路由查找的時間複雜度爲log(n),而且他批評Linux的路由項組織爲hash表,導致性能不佳,不適合商業。確實有必定道理,可仔細分析,hash表的性能真的比二叉樹差麼?二叉樹的插入和刪除某一項的時間複雜度都爲log(n);hash表插入和刪除的時間複雜度最好爲O(1),最差爲O(n),若是選取的表項(m)足夠多,且hash函數足夠好的話,其時間複雜度爲O(n/m)(當m<=n時)。當m > n / log(n)的時候,hash表的平均表現就比二叉樹要好;且當m>=n時,其時間複雜度趨近於O(1)。m的值能夠作成可調整的,這也正顯示了內核的可定製性。不過,不要盲目樂觀,這一切都是以一個足夠好的hash函數爲前期的。測試
hash函數的優劣網站
如何斷定一個hash函數的好壞呢?
hash的中文意思是「散列」,可解釋爲:分散排列。一個好的hash函數應該作到對全部元素平均分散排列,儘可能避免或者下降他們之間的衝突(Collision)。有必要再次提醒你們的是,hash函數的選擇必須慎重,若是不幸全部的元素之間都產生了衝突,那麼hash表將退化爲鏈表,其性能會大打折扣,時間複雜度迅速降爲O(n),絕對不要存在任何僥倖心理,由於那是至關危險的。歷史上就出現過利用Linux內核hash函數的漏洞,成功構造出大量使hash表發生碰撞的元素,致使系統被DoS,因此目前內核的大部分hash函數都有一個隨機數做爲參數進行摻雜,以使其最後的值不能或者是不易被預測。這又對 hash函數提出了第二點安全方面的要求:hash函數最好是單向的,而且要用隨機數進行摻雜。提到單向,你也許會想到單向散列函數md4和md5,很不幸地告訴你,他們是不適合的,由於hash函數須要有至關好的性能。
Linux內核裏面用的jhash是一個久經考驗,並被實踐證實經得起考驗的hash函數,能夠CPMS(Copy Paste Modify Save)之。Jhash的做者Bob Jenkins在其網站上還公佈了諸如針對能預知的數據進行hash的hash函數--完美(perfect)hash函數等一系列其餘hash函數。
bucket的英文解釋:
Hash table lookup operations are often O(n/m) (where n is the number of objects in the table and m is the number of buckets), which is close to O(1), especially when the hash function has spread the hashed objects evenly through the hash table, and there are more hash buckets than objects to be stored.
能夠這樣理解:
· 一個HASH的結果所對應的地址可存放兩個BUCKET。可解決HASH衝突。
· 要存數據時,第一次HASH到這裏,在第一個BUCKET存放一個數據。
· 要存數據時,當第二次因某些緣由HASH到這裏時,在第二個BUCKET存放另外一個數據。
一個由5個buckets組成的哈希表,裏面有7個元素:
linux的hash函數hash_long等,用了golden ratio來計算。由於桶(bits)的數量須要由hash函數和對衝突的指望來決定,那麼對於hash_long這樣的hash函數,咱們怎麼肯定桶的數量呢?
通常狀況下都是本身根據數據特性來考慮使用的 hash 算法,不是千篇一概咬死一個不放,好比存放 IP 地址的 hash table,用一個 65536 的桶就很好,把 IP 的後 16bit 做爲 key。這種方法絕對比 hash_long、jhash 等函數的碰撞率低。
其實就是這個界和性能的折中。我能夠取我問題空間的最大值。這樣確定能保證鍵值分散。可是這樣會浪費不少空間。然而取得過小,又影響查找效率。感受仍是要在試驗中進行測試。並且hash比其餘搜索的數據結構靈活的地方就是它的可定製性。能夠根據具體狀況調整,以達到最優的效果。