php Hash Table(二) Hash函數

哈希表最關鍵的幾個方面有:php

  1. 經過key訪問(經過哈希函數計算出key)
  2.  映射到數據結構中(哈希表自己的存儲結構)
  3. 映射的處理(衝突或者碰撞檢測和處理函數)

理解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; }

nTableMask

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; }
相關文章
相關標籤/搜索