HashTable 是 Zend Engine 核心數據結構,支撐 PHP 數組、對象屬性、符號表等。上一節介紹了基本增刪改查,本文深入講解遍歷回調、內部指針迭代、析構銷毀、比較排序、極值查找等高級 API,並給出 C 代碼實現模式。
typedef int (*apply_func_t)(void *pDest TSRMLS_DC); void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC);
回調返回值控制流程:
| 常量 | 含義 | PHP 等價 |
|---|---|---|
ZEND_HASH_APPLY_KEEP | 繼續下一個元素 | continue |
ZEND_HASH_APPLY_STOP | 立即停止遍歷 | break |
ZEND_HASH_APPLY_REMOVE | 刪除當前元素並繼續 | unset($arr[$key]); continue; |
typedef int (*apply_func_arg_t)(void *pDest, void *argument TSRMLS_DC); void zend_hash_apply_with_argument(HashTable *ht, apply_func_arg_t func, void *data TSRMLS_DC);
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 func, int num_args, ...);
// 回調:接收 zval** 與 hash_key
int php_sample_print_zval_and_key(zval **val, int num_args, va_list args, zend_hash_key *hash_key) {
TSRMLS_FETCH();
// 複製 zval 避免破壞原數據
zval tmp = **val;
zval_copy_ctor(&tmp);
INIT_PZVAL(&tmp);
convert_to_string(&tmp);
php_printf("Key: ");
if (hash_key->nKeyLength) {
// 字符串鍵
PHPWRITE(hash_key->arKey, hash_key->nKeyLength);
} else {
// 數字鍵
php_printf("%lu", hash_key->h);
}
php_printf(" => Value: ");
PHPWRITE(Z_STRVAL(tmp), Z_STRLEN(tmp));
php_printf("\n");
zval_dtor(&tmp);
return ZEND_HASH_APPLY_KEEP;
}
// 調用
zend_hash_apply_with_arguments(ht, php_sample_print_zval_and_key, 0);
關鍵點:判斷鍵類型用 nKeyLength 而非 arKey,因內核可能在 arKey 留有髒數據;空字符串鍵 nKeyLength=1 (含結束符)。
對應 PHP 用戶空間 reset/key/current/next/prev/end 族函數:
| PHP 函數 | Zend API |
|---|---|
reset() | zend_hash_internal_pointer_reset(ht) |
key() | zend_hash_get_current_key_ex(ht, &str, &len, &idx, dup, pos) |
current() | zend_hash_get_current_data(ht, (void**)&data) |
next() | zend_hash_move_forward(ht) |
prev() | zend_hash_move_backwards(ht) |
end() | zend_hash_internal_pointer_end(ht) |
each() | move_forward + get_current_key + get_current_data |
void php_sample_iterate_hash(HashTable *ht) {
for (zend_hash_internal_pointer_reset(ht);
zend_hash_has_more_elements(ht) == SUCCESS;
zend_hash_move_forward(ht)) {
char *key; uint key_len; ulong idx; int type;
zval **ppzval;
// 獲取鍵類型與值
type = zend_hash_get_current_key_ex(ht, &key, &key_len, &idx, 0, NULL);
if (zend_hash_get_current_data(ht, (void**)&ppzval) == FAILURE) continue;
// 複製並轉字符串輸出
zval tmp = **ppzval;
zval_copy_ctor(&tmp);
INIT_PZVAL(&tmp);
convert_to_string(&tmp);
php_printf("Key: ");
if (type == HASH_KEY_IS_STRING) {
PHPWRITE(key, key_len);
} else {
php_printf("%lu", idx);
}
php_printf(" => ");
PHPWRITE(Z_STRVAL(tmp), Z_STRLEN(tmp));
php_printf("\n");
zval_dtor(&tmp);
}
}
HASH_KEY_IS_STRING:字符串鍵,arKey/nKeyLength 有效。HASH_KEY_IS_LONG:數字鍵,h 有效。HASH_KEY_NON_EXISTANT:指針越界(已到末尾)。// 字符串鍵 zend_hash_del(ht, arKey, nKeyLen); // 數字鍵 zend_hash_index_del(ht, h);
返回 SUCCESS/FAILURE。刪除時會調用 HashTable 初始化時指定的析構函數 (如 ZVAL_PTR_DTOR)。
// 僅清空元素,保留 HashTable 結構 zend_hash_clean(ht); // 完全銷毀:clean + 釋放 buckets 數組 + 釋放 HashTable 結構體 zend_hash_destroy(ht);
int sample_strvec_handler(int argc, char **argv TSRMLS_DC) {
HashTable *ht;
ALLOC_HASHTABLE(ht); // 1. 分配結構體
if (zend_hash_init(ht, argc, NULL, ZVAL_PTR_DTOR, 0) == FAILURE) { // 2. 初始化
FREE_HASHTABLE(ht);
return FAILURE;
}
// 3. 插入數據
while (argc--) {
zval *val;
MAKE_STD_ZVAL(val);
ZVAL_STRING(val, argv[argc], 1);
if (zend_hash_next_index_insert(ht, (void**)&val, sizeof(zval*)) == FAILURE) {
zval_ptr_dtor(&val); // 插入失敗需手動銷毀 zval
}
}
// 4. 業務處理
process_hashtable(ht);
// 5. 銷毀
zend_hash_destroy(ht); // 調用析構函數銷毀所有 zval,釋放 buckets
FREE_HASHTABLE(ht); // 釋放 HashTable 結構體
return SUCCESS;
}
typedef int (*compare_func_t)(void *a, void *b TSRMLS_DC); // 返回 -1 (ab),同 qsort
int zend_hash_minmax(HashTable *ht, compare_func_t compar, int flag, void **pData TSRMLS_DC); // flag=0 返回最小值,flag=1 返回最大值,結果存入 pData
int fname_compare(zend_function *a, zend_function *b TSRMLS_DC) {
return strcasecmp(a->common.function_name, b->common.function_name);
}
void php_sample_funcname_sort(TSRMLS_D) {
zend_function *fe;
if (zend_hash_minmax(EG(function_table), fname_compare, 0, (void**)&fe) == SUCCESS) {
php_printf("Min function: %s\n", fe->common.function_name);
}
if (zend_hash_minmax(EG(function_table), fname_compare, 1, (void**)&fe) == SUCCESS) {
php_printf("Max function: %s\n", fe->common.function_name);
}
}
int zend_hash_compare(HashTable *hta, HashTable *htb,
compare_func_t compar, zend_bool ordered TSRMLS_DC);
// 先比元素個數,再逐元素調用 compar 比較
// ordered=1 要求順序一致,0 則無序比較(集合語義)
typedef void (*sort_func_t)(void **buckets, size_t count, size_t size, compare_func_t comp TSRMLS_DC); int zend_hash_sort(HashTable *ht, sort_func_t sort_func, compare_func_t compar, int renumber TSRMLS_DC);
sort_func:通常傳 zend_qsort (快速排序實現)。renumber=1:排序後重建數字索引 (0,1,2...),丟棄原鍵;0 保持原鍵不變。int array_data_compare(zval **a, zval **b TSRMLS_DC) {
// 簡單數值比較,實際需處理類型轉換
return Z_LVAL_PP(a) - Z_LVAL_PP(b);
}
zend_hash_sort(target_hash, zend_qsort, array_data_compare, 1 TSRMLS_CC);
zend_hash_apply_with_argument 平衡靈活性與性能。ALLOC_HASHTABLE → init → 操作 → destroy → FREE,嚴格配對。compare_func_t,配合 zend_qsort 實現高效排序。zend_hash_minmax 避免全量遍歷排序的開銷。掌握這些 API,能在擴展中高效操作 PHP 數組、符號表、對象屬性等核心結構。