php擴展開發-哈希表

什麼是哈希表呢?哈希表在數據結構中也叫散列表。是根據鍵名通過hash函數計算後,映射到表中的一個位置,來直接訪問記錄,加快了訪問速度。在理想狀況下,哈希表的操做時間複雜度爲O(1)。數據項能夠在一個與哈希表長度無關的時間內,計算出一個值hash(key),在固定時間內定位到一個桶(bucket,表示哈希表的一個位置),主要時間消耗在於哈希函數計算和桶的定位。

在分析PHP中HashTable實現原理以前,先介紹一下相關的基本概念:php

以下圖例子,但願經過人名檢索一個數據,鍵名經過哈希函數,獲得指向bucket的指針,最後訪問真實的bucket。算法

 

鍵名(Key):在哈希函數轉換前,數據的標識。數組

桶(Bucket):在哈希表中,真正保存數據的容器。數據結構

哈希函數(Hash Function):將Key經過哈希函數,獲得一個指向bucket的指針。MD5,SHA-1是咱們在業務中經常使用的哈希函數。app

哈希衝突(Hash Collision):兩個不一樣的Key,通過哈希函數,獲得同一個bucket的指針。函數

如今咱們來看一下PHP中的哈希表結構ui

 1 //Zend/zend_hash.h
 2 
 3  typedef struct _hashtable {
 4         uint nTableSize;                    //哈希表的長度,不是元素個數
 5         uint nTableMask;                  //哈希表的掩碼,設置爲nTableSize-1
 6         uint nNumOfElements;          //哈希表實際元素個數
 7         ulong nNextFreeElement;      //指向下一個空元素位置
 8         Bucket *pInternalPointer;       //用於遍歷哈希表的內部指針
 9         Bucket *pListHead;               //哈希表隊列的頭部
10         Bucket *pListTail;                 //哈希表隊列的尾部
11         Bucket **arBuckets;               //哈希表存儲的元素數組
12         dtor_func_t pDestructor;          //哈希表的元素析構函數指針
13         zend_bool persistent;              //是不是持久保存,用於pmalloc的參數,能夠持久存儲在內存中
14         unsigned char nApplyCount;     // zend_hash_apply的次數,用來限制嵌套遍歷的層數,限制爲3層
15         zend_bool bApplyProtection;     //是否開啓嵌套遍歷保護
16 #if ZEND_DEBUG
17         int inconsistent; //debug字段,查看哈希表的操做記錄
18 #endif
19 } HashTable;
20 
21  typedef struct bucket {
22         ulong h;                               //數組索引的哈希值
23         uint nKeyLength;                  //索引數組爲0,關聯數組爲key的長度
24         void *pData;                         //元素內容的指針
25         void *pDataPtr;                    // 若是是指針大小的數據,用pDataPtr直接存儲,pData指向pDataPtr
26         struct bucket *pListNext;     //哈希鏈表中下一個元素
27         struct bucket *pListLast;     //哈希鏈表中上一個元素
28         struct bucket *pNext;          //解決哈希衝突,變爲雙向鏈表,雙向鏈表的下一個元素
29         struct bucket *pLast;          //解決哈希衝突,變爲雙向鏈表,雙向鏈表的上一個元素
30         const char *arKey;             //最後一個元素key的名稱
31 } Bucket;

哈希表的經常使用操做函數,內核使用宏定義來方便咱們的操做spa

//初始化哈希表
#define
zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent) _zend_hash_init((ht), (nSize), (pDestructor), (persistent) ZEND_FILE_LINE_CC)
ht 指向哈希表的指針,一般咱們能夠這樣定義哈希表,HashTable *ht;ALLOC_HASHTABLE(ht);
nSize 哈希表的數量,哈希表老是以2N次遞增的,因此實際的數量會大於你傳遞的數量
pHashFunction 這是早期用到的一個參數,用來定義一個hash函數,如今所有改爲默認的DJBX33A算法計算哈希值,只是爲了兼容才保留了參數,咱們傳NULL便可
pDestructor 是一個回調函數,當咱們刪除或修改hashtable表中的一個元素時便會調用改函數
persistent 是一個標識位,是否在內存中永久保存ht指向的哈希表。可使用1或0兩個值,顯然1表示永久保存
//更新哈希表的關聯數組值
#define
zend_hash_update(ht, arKey, nKeyLength, pData, nDataSize, pDest) \
_zend_hash_add_or_update(ht, arKey, nKeyLength, pData, nDataSize, pDest, HASH_UPDATE ZEND_FILE_LINE_CC)
ht 同上
arKey 字符索引的key值
nKeyLength key長度
pData 字符數組保存的值
nDataSize sizeof(pData)的值
pDest 若是不爲NULL,則*pDest=pData;

//插入哈希表的關聯數組數據  #define zend_hash_add(ht, arKey, nKeyLength, pData, nDataSize, pDest) \ _zend_hash_add_or_update(ht, arKey, nKeyLength, pData, nDataSize, pDest, HASH_ADD ZEND_FILE_LINE_CC)
參數同上 //更新索引數組
#define zend_hash_index_update(ht, h, pData, nDataSize, pDest) \ _zend_hash_index_update_or_next_insert(ht, h, pData, nDataSize, pDest, HASH_UPDATE ZEND_FILE_LINE_CC)
h 數字索引值
其他參數同上
//插入索引數組
#define zend_hash_next_index_insert(ht, pData, nDataSize, pDest) \ _zend_hash_index_update_or_next_insert(ht, 0, pData, nDataSize, pDest, HASH_NEXT_INSERT ZEND_FILE_LINE_CC)
參數同上

 哈希表的APIdebug

int zend_hash_init(HashTable* ht, uint size, hash_func_t hash, dtor_func_t destructor, zend_bool persistent)
int zend_hash_add(HashTable* ht, const char* key, uint klen, void* data, uint dlen, void** dest)
int zend_hash_update(HashTable* ht, const char* key, uint klen, void* data, uint dlen, void** dest)
int zend_hash_find(HashTable* ht, const char* key, uint klen, void** data)
zend_bool zend_hash_exists(HashTable* ht, const char* key, uint klen)
int zend_hash_del(HashTable* ht, const char* key, uint klen)
int zend_hash_index_update(HashTable* ht, ulong index, void* data, uint dsize, void** dest)
int zend_hash_index_del(HashTable* ht, ulong index)
int zend_hash_index_find(HashTable* ht, ulong index, void** data)
int zend_hash_index_exists(HashTable* ht, ulong index)ulong zend_hash_next_free_element(HashTable* ht)

哈希表的遍歷API指針

HashTable Traversal API
int zend_hash_internal_pointer_reset(HashTable* ht)
resets the internal pointer of ht to the start
int zend_hash_internal_pointer_reset_ex(HashTable* ht, HashPosition position)
sets position the the start of ht
int zend_hash_get_current_data(HashTable* ht, void* data)
gets the data at the current position in ht, data should be cast to void**, ie: (void**) &data
int zend_hash_get_current_data_ex(HashTable* ht, void* data, HashPosition position)
sets data to the data at position in ht
int zend_hash_get_current_key(HashTable* ht, void* data, char**key, uint klen, ulong index, zend_bool duplicate)
sets key, klen, and index from the key information at the current position. The possible return values HASH_KEY_IS_STRING and HASH_KEY_IS_LONG are indicative of the kind of key found at the current posision.
int zend_hash_get_current_key_ex(HashTable* ht, void* data, char**key, uint klen, ulong index, zend_bool duplicate, HashPosition position)
sets key, klen, and index from the key information at position. The possible return values HASH_KEY_IS_STRING and HASH_KEY_IS_LONG are indicative of the kind of key found at position.
int zend_hash_move_forward(HashTable* ht)
moves the internal pointer of ht to the next entry in ht
int zend_hash_move_forward_ex(HashTable* ht, HashPosition position)
moves position to the next entry in ht

經過一個例子來使用上面的API函數

PHP_FUNCTION(myext_example_hashtable);//php_myext.h申明

PHP_FE(myext_example_hashtable, NULL)//函數註冊

PHP_FUNCTION(myext_example_hashtable){
    php_printf("init\n");
    HashTable *myht;
    ALLOC_HASHTABLE(myht);
    int nSize = 100;
    zend_hash_init(myht, nSize, NULL, NULL, 0);//哈希函數和析構函數都爲NULL 
    char *key1 = "key1";
    int nKeyLength = sizeof(key1);
    zval * value1;
    MAKE_STD_ZVAL(value1);
    ZVAL_STRING(value1,"value1",0);
    zval * value2;
    MAKE_STD_ZVAL(value2);
    ZVAL_STRING(value2,"value2",0);
    int ret = zend_hash_add(myht, key1, nKeyLength+1, &value1, sizeof(zval*),NULL);
    printf("zend_hash_add,ret=>%d\n",ret);
    ret = zend_hash_add(myht, key1, nKeyLength+1, &value2, sizeof(zval*),NULL);
    printf("add exist key , zend_hash_add,ret=>%d\n",ret);
    ret = zend_hash_update(myht, key1, nKeyLength+1, &value2, sizeof(zval*),NULL);
    printf("update exist key , zend_hash_add,ret=>%d\n",ret);
    ret = zend_hash_index_update(myht,0,&value2,sizeof(zval*),NULL);
    printf("zend_hash_index_update,ret=>%d\n",ret);
    ret = zend_hash_next_index_insert(myht,&value2,sizeof(zval*),NULL);
    printf("zend_hash_next_index_insert,ret=>%d\n",ret);


    HashPosition position;
    zval **data = NULL;

    php_printf("\n");
    for (zend_hash_internal_pointer_reset_ex(myht, &position);
            zend_hash_get_current_data_ex(myht, (void**) &data, &position) == SUCCESS;
            zend_hash_move_forward_ex(myht, &position)) {

        /* by now we have data set and can use Z_ macros for accessing type and variable data */

        char *key = NULL;
        uint  klen;
        ulong index;

        if (zend_hash_get_current_key_ex(myht, &key, &klen, &index, 0, &position) == HASH_KEY_IS_STRING) {
            /* the key is a string, key and klen will be set */
            php_printf("string key %s =>",key);
        } else {
            /* we assume the key to be long, index will be set */
            php_printf("index key %d =>",index);
        }
        if (Z_TYPE_PP(data) != IS_STRING) {
            convert_to_long(*data);
        }
        PHPWRITE(Z_STRVAL_PP(data), Z_STRLEN_PP(data));
        php_printf("\n");
    }


    FREE_ZVAL(value1);
    FREE_ZVAL(value2);
    zend_hash_destroy(myht);
    FREE_HASHTABLE(myht);
    RETURN_NULL();
}

還可使用內核把哈希表封裝成數組的方式使用,也就是zval類型裏面的IS_ARRAY

array_init(arrval);
 
add_assoc_long(zval *arrval, char *key, long lval);
add_index_long(zval *arrval, ulong idx, long lval);
add_next_index_long(zval *arrval, long lval);

//add_assoc_*系列函數:
add_assoc_null(zval *aval, char *key);
add_assoc_bool(zval *aval, char *key, zend_bool bval);
add_assoc_long(zval *aval, char *key, long lval);
add_assoc_double(zval *aval, char *key, double dval);
add_assoc_string(zval *aval, char *key, char *strval, int dup);
add_assoc_stringl(zval *aval, char *key,char *strval, uint strlen, int dup);
add_assoc_zval(zval *aval, char *key, zval *value);
 
//備註:其實這些函數都是宏,都是對add_assoc_*_ex函數的封裝。
 
//add_index_*系列函數:
ZEND_API int add_index_long     (zval *arg, ulong idx, long n);
ZEND_API int add_index_null     (zval *arg, ulong idx           );
ZEND_API int add_index_bool     (zval *arg, ulong idx, int b    );
ZEND_API int add_index_resource (zval *arg, ulong idx, int r    );
ZEND_API int add_index_double   (zval *arg, ulong idx, double d);
ZEND_API int add_index_string   (zval *arg, ulong idx, const char *str, int duplicate);
ZEND_API int add_index_stringl  (zval *arg, ulong idx, const char *str, uint length, int duplicate);
ZEND_API int add_index_zval     (zval *arg, ulong index, zval *value);
 
//add_next_index_long函數:
ZEND_API int add_next_index_long        (zval *arg, long n  );
ZEND_API int add_next_index_null        (zval *arg          );
ZEND_API int add_next_index_bool        (zval *arg, int b   );
ZEND_API int add_next_index_resource    (zval *arg, int r   );
ZEND_API int add_next_index_double      (zval *arg, double d);
ZEND_API int add_next_index_string      (zval *arg, const char *str, int duplicate);
ZEND_API int add_next_index_stringl     (zval *arg, const char *str, uint length, int duplicate);
ZEND_API int add_next_index_zval        (zval *arg, zval *value);
相關文章
相關標籤/搜索