php底層HashTable的實現

本文轉載自:  http://segmentfault.com/blog/tree/1190000000718519php

 

 

HashTable對PHP來講是一種很是重要的數據結構。不少PHP的內部實現(變量的做用域,函數表,類的屬性、方法,數組)就是經過HashTable來實現的。最近了解了一下PHP底層HashTable的實現。前端

PHP底層HashTable的實現有兩個很是重要的結構分別是:HashTable和Bucket。segmentfault

先說一下HashTable結構:數組

HashTable的底層實現代碼以下:網絡

typedef struct _hashtable{數據結構

    uint nTableSize;         // hash Bucket的大小,最小爲8函數

    uint nTableMask;         //nTableSize - 1, 索引取值的優化優化

    uint nNumofElements      // bucket 裏面存的總數 ui

    ulong nNextFreeElement   //下一個數字索引的位置spa

    Bucket *pInternalPointer  //當前遍歷的指針(foreach比較快的緣由)

    Bucket *pListHead         //整個hashtable的頭指針

    Bucket *pListTail         //整個hashTable的尾指針

    Bucket **argBuckets       // Buceket 數組,用來存儲數據

    doctor_func_t pDestructor //刪除元素時的回調函數,用於資源的釋放

    zend_bool persistent      //Bucket的內存分配方式,true使用系統的分配函數,false 使用php的內存分配函數

    unsigned char nApplyCount //標記當前hash bucket 被遞歸的次數

    zend_bool bApplyProtection 

#if ZEND_DEBUG

    int inconsistent           

#endif 

}HashTable

建議不太瞭解hash數據結構的同窗先簡單瞭解一下hash結構。

簡單說一下php中hashtable的初始化操做:

代碼以下:

 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;

    //...

    if (nSize >= 0x80000000) {

        /* prevent overflow */

        ht->nTableSize = 0x80000000;

    } else {

        while ((1U << i) < nSize) {

            i++;

        }

        ht->nTableSize = 1 << i;

    }

    // ...

    ht->nTableMask = ht->nTableSize - 1;

 

    /* Uses ecalloc() so that Bucket* == NULL */

    if (persistent) {

        tmp = (Bucket **) calloc(ht->nTableSize, sizeof(Bucket *));

        if (!tmp) {

            return FAILURE;

        }

        ht->arBuckets = tmp;

    } else {

        tmp = (Bucket **) ecalloc_rel(ht->nTableSize, sizeof(Bucket *));

        if (tmp) {

            ht->arBuckets = tmp;

        }

    }

 

    return SUCCESS;

}

最開始判斷須要初始化的hashtable大小是否是超過了系統能使用的最大大小。下面是對tablesize大小的一個處理。將用戶自定義的大小改爲須要的大小。例如:若是用戶定義的hashtable大小是6,那初始化時,就會將6變成8,若是用戶定義的大小爲11,那初始化後的Hashtable的大小爲16.

下面就是一個簡單的判斷,來決定是按照C語言自己的分配內存函數來分配內存,仍是根據php封裝好的內存分配函數來分配內存。

再談一下 bucket的結構

typedef struct bucket{

    ulong h;       //對key索引之後的值,數字key不作kash

    uint nKeyLength; //key的長度

    void *pData;     

    void *pDataPtr;   //指針數據,指向真實數據

    struct bucket * pListNext; //整個hash表的下個元素

    struct bucket *pListLast;   //整個hash表的上個元素

    struct bucket *pNext;       //本bucket裏面,下一個元素

    struct bucket *pLast;       //本bucket裏面的上一個元素

    char arKey[1];

}Bucket

這裏用一張網絡上的很火的圖來講明(圖原地址沒找到,沒有作來源說明):

 

下面是引用了tipi裏面的插入說明:

引用地址:tipi

如圖中左下角的假設,假設依次插入了Bucket1,Bucket2,Bucket3三個元素:

一、插入Bucket1時,哈希表爲空,通過哈希後定位到索引爲1的槽位。此時的1槽位只有一個元素Bucket1。 其中Bucket1的pData或者pDataPtr指向的是Bucket1所存儲的數據。此時因爲沒有連接關係。pNext, pLast,pListNext,pListLast指針均爲空。同時在HashTable結構體中也保存了整個哈希表的第一個元素指針, 和最後一個元素指針,此時HashTable的pListHead和pListTail指針均指向Bucket1。

二、插入Bucket2時,因爲Bucket2的key和Bucket1的key出現衝突,此時將Bucket2放在雙鏈表的前面。 因爲Bucket2後插入並置於鏈表的前端,此時Bucket2.pNext指向Bucket1,因爲Bucket2後插入。 Bucket1.pListNext指向Bucket2,這時Bucket2就是哈希表的最後一個元素,這是HashTable.pListTail指向Bucket2。\三、插入Bucket3,該key沒有哈希到槽位1,這時Bucket2.pListNext指向Bucket3,由於Bucket3後插入。 同時HashTable.pListTail改成指向Bucket3。

簡單來講就是哈希表的Bucket結構維護了哈希表中插入元素的前後順序,哈希表結構維護了整個哈希表的頭和尾。 在操做哈希表的過程當中始終保持預算之間的關係。

相關文章
相關標籤/搜索