一、建立HashTable
int zend_hash_init(HashTable *ht,//指向一個HashTableuint 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,//待操作的htchar *arKey,//索引,如"my_key"uint nKeyLen,//字串索引的長度,如6void **pData,//要插入的資料,注意它是void **類型的。uint nDataSize,//例如,存放zval類型變數,那麼nDataSize=sizeof(zval*)void *pDest//如果操作成功,則pDest=*pData;);int zend_hash_next_index_insert(HashTable *ht,//待操作的htvoid *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);
這兩個函數返回SUCCESS或者FAILURE,分別代表著是否存在
六、加速
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);
zend_hash_merge()與zend_hash_copy唯一的不同便是多了個int類型的overwrite參數,當其值非0的時候,兩個函數的工作是完全一樣的;如果overwrite參數為0,則zend_hash_merge函數就不會對target中已有索引的值進行替換了。
void zend_hash_merge_ex(HashTable *target,HashTable *source,copy_ctor_func_t pCopyConstructor, uint size,merge_checker_func_t pMergeSource,void *pParam);
zend_hash_merge_ex函數又繁瑣了些,與zend_hash_copy相比,其多了兩個參數,多出來的pMergeSoure回呼函數允許我們選擇性的進行merge,而不是全都merge。
八、遍曆
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迴圈。
<?phpfunction 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=0convert_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的參數$arrif( 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中帶索引的遍曆
<?phpforeach($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的參數$arrif( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) ){RETURN_NULL();}char *a="var1", *b="var2"; //可變參數a,bzend_hash_apply_with_arguments(arrht, php_foreach_body_and_key, 2, &a, &b);//開始遍曆,並傳遞2個可參數}