一、创建HashTable
- int zend_hash_init(
- HashTable *ht,//指向一个HashTable
- uint nSize,//nSize是指这个HashTable可以拥有的元素的最大数量。在我们添加新的元素时,这个值会根据情况决定是否自动增长,这个值永远都是2的次方,如果你给它的值不是一个2的次方
- //的形式,那它将自动调整成大于它的最小的2的次方值。它的计算方法就像这样:nSize = pow(2, ceil(log(nSize, 2)))。(HashTable能够包含任意数量的元素,这个值只是为了提前申请好内存,提高性
- //能,省的不停的进行rehash操作)
- hash_func_t pHashFunction,//pHashFunction是早期的参数,为向前兼容所以没有去掉。直接赋值NULL即可。
- dtor_func_t pDestructor,//一个回调函数,当我们删除或者修改HashTable中其中一个元素时候便会调用,它的函数原型必须是这样的:void method_name(void *pElement),一般使用宏ZVAL_PTR_DTOR即可。
- zend_bool persistent//值为0或1,如果是1那么这个HashTable将永远存在于内存中,而不会在RSHUTDOWN阶段自动被注销掉。此时第一个参数ht所指向的地址必须是通过pemalloc()函数申请的。
- );
注:
#define ZVAL_PTR_DTOR (void (*)(void *)) zval_ptr_dtor_wrapper
删除元素,回调析构该元素。
二、添加
- int zend_hash_add(
- HashTable *ht, //待操作的ht
- char *arKey, //索引,如"my_key"
- uint nKeyLen, //字符串索引的长度,如6
- void **pData, //要插入的数据,注意它是void **类型的。
- uint nDataSize, //例如,存放zval类型变量,那么nDataSize=sizeof(zval*)
- void *pDest //如果操作成功,则pDest=*pData;
- );
- int zend_hash_next_index_insert(
- HashTable *ht, //待操作的ht
- void *pData, //要插入的数据
- uint nDataSize,
- void **pDest
- );
三、更新
- int zend_hash_update(
- HashTable *ht,
- char *arKey, //索引字符串
- uint nKeyLen, //索引字符串长度
- void *pData,
- uint nDataSize,
- void **pDest
- );
- int zend_hash_index_update(
- HashTable *ht,
- ulong h,
- void *pData,
- uint nDataSize,
- void **pDest
- );
四、查找
- int zend_hash_find(
- HashTable *ht,
- char *arKey, //索引字符串
- uint nKeyLength, //索引字符串长度
- void **pData //找到元素,则将元素指向pData
- );
- int zend_hash_index_find(
- HashTable *ht,
- ulong h, //数字索引
- void **pData //找到元素,则将元素指向pData
- );
五、检测
- int zend_hash_exists(
- HashTable *ht,
- char *arKey,
- uint nKeyLen
- );
- int zend_hash_index_exists(
- HashTable *ht,
- ulong h
- );
六、加速
ulong zend_get_hash_value(char *arKey, uint nKeyLen);//返回索引的hash值
通过使用zend_get_hash_value函数得到索引hash值,之后对hashTable进行添加、修改等操作使用quick系列函数可以避免重复计算字符串的hash值,达到加速加速的目的。
- int zend_hash_quick_add(
- HashTable *ht,
- char *arKey,
- uint nKeyLen,
- ulong hashval, //zend_get_hash_value函数得到的hash值
- void *pData,
- uint nDataSize,
- void **pDest
- );
- int zend_hash_quick_update(
- HashTable *ht,
- char *arKey,
- uint nKeyLen,
- ulong hashval,
- void *pData,
- uint nDataSize,
- void **pDest
- );
- int zend_hash_quick_find(
- HashTable *ht,
- char *arKey,
- uint nKeyLen,
- ulong hashval,
- void **pData
- );
- int zend_hash_quick_exists(
- HashTable *ht,
- char *arKey,
- uint nKeyLen,
- ulong hashval
- );
七、数组之间的复制与合并
- void zend_hash_copy(
- HashTable *target, //*source中的所有元素都会通过pCopyConstructor函数Copy到*target中去
- HashTable *source, //target中原有的与source中索引位置的数据会被替换掉,而其它的元素则会被保留,原封不动。
- copy_ctor_func_t pCopyConstructor,
- void *tmp,//tmp参数是为了兼容PHP4.0.3以前版本的,现在赋值为NULL即可。
- uint size //size参数代表每个元素的大小,对于PHP语言中的数组来说,这里的便是sizeof(zval*)了。
- );
- void zend_hash_merge(
- HashTable *target,
- HashTable *source,
- copy_ctor_func_t pCopyConstructor,
- void *tmp,
- uint size,
- int overwrite
- );
- void zend_hash_merge_ex(
- HashTable *target,
- HashTable *source,
- copy_ctor_func_t pCopyConstructor,
- uint size,
- merge_checker_func_t pMergeSource,
- void *pParam
- );
八、遍历
PHP中数组的本质是HashTable,可以使用foreach来遍历PHP中的数组。在内核中则是使用zend_hash_apply函数。
zend_hash_apply接收一个回调函数,并将HashTable的每一个元素都传递给回调函数。这里的回调函数相当于PHP中的foreach循环主体。
- 回调函数的返回值有一个共同的约定:
- ZEND_HASH_APPLY_KEEP 结束当前请求,进入下一个循环。与PHP语言forech语句中的一次循环执行完毕或者遇到
- continue关键字的作用一样。
- ZEND_HASH_APPLY_STOP 跳出,与PHP语言forech语句中的break关键字的作用一样。
- ZEND_HASH_APPLY_REMOVE 删除当前的元素,然后继续处理下一个。相当于在PHP语言中:unset($foo
- [$key]);continue;
1、无索引遍历
typedef int (*apply_func_t)(void *pDest TSRMLS_DC);
void zend_hash_apply(HashTable *ht,apply_func_t apply_func TSRMLS_DC);
看看PHP语言中的forech循环。
- <?php
- function foreach_test($arr){
- foreach($arr as $val) {
- echo "The value is: $val\n";
- }
- }
- ?>
在内核中实现函数foreach_test。
- //zend_hash_apply回调函数,相当于PHP中的foreach主体
- int php_foreach_body(zval **zval TSRMLS_DC){
- zval tmpcopy = **zval; //重新copy一个zval,防止破坏原数据
- zval_copy_ctor(&tmpcopy);
- INIT_PZVAL(&tmpcopy); //初始化refcount__gc=1, is_ref__gc=0
- convert_to_string(&tmpcopy); //转换为字符串
- php_printf("The value is:");
- PHPWRITE(Z_STRVAL(tmpcopy), Z_STRLEN(tmpcopy));
- php_printf("\n");
- zval_dtor(&tmpcopy);
- return ZEND_HASH_APPLY_KEEP;
- }
- PHP_FUNCTION(foreach_test){
- zval *arr; //相当于php中function的参数$arr
- if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) ){
- RETURN_NULL();
- }
- zend_hash_apply(Z_ARRVAL_P(arr), php_foreach_body TSRML_CC); //开始遍历
- }
2、有索引遍历
为了能在遍历时同时接收索引的值,可以使用void zend_hash_apply_with_arguments:
- typedef int (*apply_func_args_t)(void *pDest,int num_args, va_list args, zend_hash_key *hash_key);
- void zend_hash_apply_with_arguments(
- HashTable *ht,
- apply_func_args_t apply_func,
- int numargs, //传递参数的个数,通过指定参数个数,决定va_end()
- ...
- );//这个函数通过C语言中的可变参数特性来接收参数。
PHP中带索引的遍历
- <?php
- foreach($arr as $key => $val)
- {
- echo "The value of $key is: $val\n";
- }
- ?>
内核中实现:
- int php_foreach_body_and_key(zval **val, int num_args, va_list args, zend_hash_key *hash_key){
- zval tmpcopy = **val; //重新copy一个zval,防止破坏原数据
- php_printf("附带参数个数%d<br>", num_args);
- char **a, **b;
- a = va_arg(args, char**); //args是可变参数的起始地址
- b = va_arg(args, char**);
- php_printf("参数1:%s, 参数2:%s<br>", *a, *b);
- INIT_PZVAL(&tmpcopy);
- zval_copy_ctor(&tmpcopy);
- convert_to_string(&tmpcopy);
- php_printf("The value of ");
- if( hash_key->nKeyLength ){
- PHPWRITE(hash_key->arKey, hash_key->nKeyLength);
- }else{
- php_printf("%ld", hash_key->h);
- }
- php_printf(" is: ");
- PHPWRITE(Z_STRVAL(tmpcopy), Z_STRLEN(tmpcopy));
- php_printf("\n");
- zval_dtor(&tmpcopy);
- return ZEND_HASH_APPLY_KEEP;
- }
- PHP_FUNCTION(foreach_test){
- zval *arr; //相当于php中function的参数$arr
- if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) ){
- RETURN_NULL();
- }
- char *a="var1", *b="var2"; //可变参数a,b
- zend_hash_apply_with_arguments(arrht, php_foreach_body_and_key, 2, &a, &b); //开始遍历,并传递2个可参数
- }