哈希表最關鍵的幾個方面有:php
通常來講對於整形索引進行哈希咱們很容易想到的是取模運算,好比array(1=>'a', 2=>'b', 3=>'c'),這類咱們可使用index%3來哈希,不過PHP數組的下標還有更靈活的array('a'='c', 'b'=>'d'),此時選擇什麼哈希函數?答案是DJBX33A算法。html
PS:DJBX33A算法,也就是time33算法,是APR默認哈希算法,php, apache, perl, bsddb也都使用time33哈希。對於33這個數,DJB註釋中是說,1到256之間的全部奇數,都能達到一個可接受的哈希分佈,平均分佈大概是86%。而其中33,17,31,63,127,129這幾個數在面對大量的哈希運算時有一個更大的優點,就是這些數字能將乘法用位運算配合加減法替換,這樣運算速度會更高。gcc編譯器開啓優化後會自動將乘法轉換爲位運算。算法
下面就是這個哈希函數的具體代碼實現:apache
更詳細的解釋看鳥哥:http://www.laruence.com/2009/07/23/994.html數組
static inline ulong zend_inline_hash_func(char *arKey, uint nKeyLength){ register ulong hash = 5381; /* variant with the hash unrolled eight times */
for (; nKeyLength >= 8; nKeyLength -= 8) { hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; } switch (nKeyLength) { case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
case 1: hash = ((hash << 5) + hash) + *arKey++; break; case 0: break; EMPTY_SWITCH_DEFAULT_CASE() } return hash; }
PHP哈希表最小容量是8(2^3),最大容量是0x80000000(2^31),並向2的整數次冪圓整(即長度會自動擴展爲2的整數次冪,如13個元素的哈希表長度爲16;100個元素的哈希表長度爲128)。nTableMask被初始化爲哈希表長度(圓整後)減1。數據結構
哈希表的掩碼數值等於 nTableSize-1,他的做用是什麼?用來糾正經過DBJ算法計算的哈希值在當前nTableSize大小的哈希表中的正確的索引值。比 如"foo"經過固定算法以後得出的哈希值是193491849,若是表的大小爲64,很明顯已經超過了最大索引值,這時候就須要運用哈希表的掩碼對其進 行矯正實際採用的方法就是與掩碼進行位與運算,這樣作是爲了把哈希值大的同樣映射到nTalbeSize空間內。函數
hash | 193491849 | 0b1011100010000111001110001001 & mask | & 63 | & 0b0000000000000000000000111111 --------------------------------------------------------- = index | = 9 | = 0b0000000000000000000000001001
具體代碼在zend/Zend_hash.c的_zend_hash_init函數中,這裏截取與本文相關的部分並加上少許註釋。優化
ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC) { uint i = 3; Bucket **tmp; SET_INCONSISTENT(HT_OK); //長度向2的整數次冪圓整 if (nSize >= 0x80000000) { /* prevent overflow */ ht->nTableSize = 0x80000000; } else { while ((1U << i) < nSize) { i++; } ht->nTableSize = 1 << i; } ht->nTableMask = ht->nTableSize - 1; /*此處省略若干代碼…*/ return SUCCESS; }
Zend HashTable的哈希算法比較簡單:網站
hash(key)=key & nTableMask
即簡單將數據的原始key與HashTable的nTableMask進行按位與便可。ui
若是原始key爲字符串,則首先使用Times33算法將字符串轉爲整形再與nTableMask按位與。
hash(strkey)=time33(strkey) & nTableMask
下面是Zend源碼中查找哈希表的代碼:
ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData) { uint nIndex; Bucket *p; IS_CONSISTENT(ht); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) { if ((p->h == h) && (p->nKeyLength == 0)) { *pData = p->pData; return SUCCESS; } p = p->pNext; } return FAILURE; } ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData) { ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); h = zend_inline_hash_func(arKey, nKeyLength); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) { if ((p->h == h) && (p->nKeyLength == nKeyLength)) { if (!memcmp(p->arKey, arKey, nKeyLength)) { *pData = p->pData; return SUCCESS; } } p = p->pNext; } return FAILURE; }
其中zend_hash_index_find用於查找整數key的狀況,zend_hash_find用於查找字符串key。邏輯基本一致,只是字符串key會經過zend_inline_hash_func轉爲整數key,zend_inline_hash_func封裝了times33算法。
關於哈希衝突,PHP的實現是經過拉鍊法實現的,當鍵值被哈希到同一個槽位(bucket)就是發生了衝突,這時候會從bucket拉出一個鏈表把衝突的元素順序連接起來。
關於那兩對指針,國外有網站上搞錯了,這裏把檢測哈希衝突的PHP函數貼出來,pNext指針的做用就一目瞭然了。
ZEND_API int zend_hash_exists(const HashTable *ht, const char *arKey, uint nKeyLength) { ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); h = zend_inline_hash_func(arKey, nKeyLength); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) { if (p->arKey == arKey || ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) { return 1; } p = p->pNext; } return 0; }