php扩展开发-哈希表
什么是哈希表呢?哈希表在数据结构中也叫散列表。是根据键名经过hash函数计算后,映射到表中的一个位置,来直接访问记录,加快了访问速度。在理想情况下,哈希表的操作时间复杂度为O(1)。数据项可以在一个与哈希表长度无关的时间内,计算出一个值hash(key),在固定时间内定位到一个桶(bucket,表示哈希表的一个位置),主要时间消耗在于哈希函数计算和桶的定位。
在分析PHP中HashTable实现原理之前,先介绍一下相关的基本概念:
如下图例子,希望通过人名检索一个数据,键名通过哈希函数,得到指向bucket的指针,最后访问真实的bucket。
键名(Key):在哈希函数转换前,数据的标识。
桶(Bucket):在哈希表中,真正保存数据的容器。
哈希函数(Hash Function):将Key通过哈希函数,得到一个指向bucket的指针。MD5,SHA-1是我们在业务中常用的哈希函数。
哈希冲突(Hash Collision):两个不同的Key,经过哈希函数,得到同一个bucket的指针。
现在我们来看一下PHP中的哈希表结构
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;
哈希表的常用操作函数,内核使用宏定义来方便我们的操作
//初始化哈希表
#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)
参数同上
哈希表的API
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);