PHP源碼分析-HashTable API

一、創建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
);
這兩個函數返回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循環。

<?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個可參數
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章