Hash數據結構是一種很是常見的數據結構,做爲一個程序員,你可能天天都在和它接觸, 儘管不少時候你可能沒有意識到。Hash在PHP內核中扮演了很是重要的角色,數組、變量做用域、函數參數列表等均是基於Hash實現。因此,在PHP裏你能看到各類對於Hash的優化。html
Hash數據結構程序員
Hash數據結構,本質上爲了解決計算機中真正意義的數組只能使用數字做爲索引,而程序員卻常常須要使用字符串或者其餘更復雜的類型來做爲數組key的問題。首先咱們須要一個函數能把字符串轉成數字。而後,因爲數組的長度有限,咱們還須要把這個數字映射到數組長度的區間裏,通常使用取模操做。最後, 若是兩個字符串最終計算出來的Hash值同樣,將保存到數組相同的位置上,這種狀況叫作Hash衝突。解決衝突的常見辦法之一就是拉鍊法, 就是將Hash衝突的數據維護在一個鏈表裏; 另外一種比較常見的解決衝突的辦法叫開放尋址。下圖所示是一個典型的Hash結構:算法
Hash函數的最優狀況是,將key平均分佈在數組空間中, 這樣能得到最大的性能。最壞的狀況是徹底衝突,這個時候Hash表事實上已經退化爲一個鏈表。前幾年比較火的Hash攻擊事實上就是構造大量的衝突key,使PHP的POST數組退化爲鏈表,從而耗盡服務器的CPU資源。數組
所以咱們能夠得出一個結論, 一個優秀的hash函數應該符合兩點:足夠隨機分佈,足夠快。服務器
DJB2算法數據結構
PHP內核中的hash函數(http://www.cse.yorku.ca/~oz/hash.html)採用的是經典的djb2算法,源碼爲:函數
關於這個算法背後其實有很複雜的數學證實,我曾經看到過一篇文章給出了完整證實,受限於數學水平這裏我只能從工程的角度來簡單說一下結論:性能
奇數和素數能比偶數獲得更隨機的結果,因此能夠看到hash函數裏使用的是5381和33兩個數。優化
爲何是5381?在這個討論裏(https://groups.google.com/forum/#!topic/comp.lang.c/lSKWXiuNOAk)談到採用一個比255大而又不是特別大的素數均可以,djb2的做者也參與了討論,他在討論裏說他選擇了5381,只是由於5381符合這個條件, 並無其餘特殊的緣由。google
爲何是33?其實有不少數均可以,採用33是由於n*33能夠優化爲n << 5 + n,能夠把一個耗時的乘法操做優化爲移位操做,畢竟hash函數的效率是很是重要的。
咱們看下PHP內核中的Hash函數實現:
能夠看出來PHP採用的就是djb2算法,可是彷佛和經典的djb2算法有些差距。實際上是PHP內核開發者對這個函數作了進一步的優化。
達夫設備
達夫設備(https://zh.wikipedia.org/wiki/%E8%BE%BE%E5%A4%AB%E8%AE%BE%E5%A4%87)的本質在於減小循環次數。考慮下面的代碼:
這段代碼的特色是很是簡單,簡單到循環自己就佔用了大多數性能開銷,由於每次循環都須要作累減和比較。所以減小循環次數反而成爲了優化重點,這種狀況在系統底層的內存複製的場景中常常出現。PHP中的Hash函數就採用了達夫設備這種技巧來優化, 循環次數減小爲原來的1/8。
順便說一句,實際上最先的達夫設備程序版本是這樣的:
並且這段代碼真的能編譯經過。
取模優化
計算出來hash值以後咱們須要把hash值映射到數組中, 好比hash值爲15,數組a長度爲10,15%10獲得5, 最終保存在a[5]的位置上。可是取模操做在特定條件下是能夠優化的。在PHP內核中,hash table的大小會設定爲2的n次方。當數組大小爲2的n次方時, hash % ht->nTableSize 能夠優化爲 hash & (ht->nTableSize - 1),也就是把除法操做優化成了位操做。
結語
咱們看到,一個簡單的hash函數背後都蘊含了如此豐富的優化技巧,難怪都說PHP是世界上最好的語言 :) 。