PHP 陣列在 Zend Engine 中以 HashTable 實現,結合雙向鏈表維持插入順序,支援 O(1) 存取、遍歷、插入、刪除。
typedef struct _zval_struct {
zvalue_value value; // 聯合體:long, double, str, ht, obj, res, ref
zend_uint refcount__gc;
zend_uchar type; // IS_ARRAY, IS_STRING, IS_LONG...
zend_uchar is_ref__gc;
} zval;
typedef struct bucket {
ulong h; // 數字鍵雜湊值 或 字串鍵雜湊
uint nKeyLength; // 0=數字鍵, >0=字串鍵長度(+1 含結尾\0)
void *pData; // 指向 zval*
void *pDataPtr; // 直接指向資料 (優化)
struct bucket *pListNext, *pListLast; // 全域插入順序鏈表
struct bucket *pNext, *pLast; // 同雜湊桶鏈表
char arKey[1]; // 字串鍵名 (柔性陣列)
} Bucket;
typedef struct _hashtable {
uint nTableSize; // 雜湊桶數量 (2 的冪)
uint nTableMask; // nTableSize - 1
uint nNumOfElements; // 元素數量
ulong nNextFreeElement; // 下一個自動數字索引
Bucket *pInternalPointer; // 遍歷指標
Bucket *pListHead, *pListTail; // 插入順序鏈表頭尾
Bucket **arBuckets; // 雜湊桶陣列
dtor_func_t pDestructor; // 元素銷毀回呼
zend_bool persistent; // 是否持久化
unsigned char nApplyCount; // 遞迴保護
zend_bool bApplyProtection;
} HashTable;
// 建立空陣列
zval *arr;
ALLOC_INIT_ZVAL(arr);
array_init(arr); // 等同於 Z_TYPE_P(arr)=IS_ARRAY; zend_hash_init(...)
// 關聯陣列新增
add_assoc_long(arr, "key", 123);
add_assoc_string(arr, "name", "value", 1); // 1=複製字串
// 數字索引新增
add_index_long(arr, 0, 456);
// 追加 (自動索引)
add_next_index_string(arr, "auto", 1);
// 子陣列
zval *sub; ALLOC_INIT_ZVAL(sub); array_init(sub);
add_next_index_string(sub, "child", 1);
add_assoc_zval(arr, "sub", sub);
| PHP 寫法 | 內核巨集 |
|---|---|
$arr[] = null; | add_next_index_null(arr); |
$arr[5] = 100; | add_index_long(arr, 5, 100); |
$arr['key'] = 'val'; | add_assoc_string(arr, "key", "val", 1); |
$arr['obj'] = $obj; | add_assoc_zval(arr, "obj", obj_zval); |
isset($arr['key']) | zend_hash_exists(Z_ARRVAL_P(arr), "key", 4); |
unset($arr['key']) | zend_hash_del(Z_ARRVAL_P(arr), "key", 4); |
count($arr) | zend_hash_num_elements(Z_ARRVAL_P(arr)) |
foreach ($arr as $k=>$v) | zend_hash_internal_pointer_reset/forward + zend_hash_get_current_data |
HashTable 獨立為 zend_array,內嵌 HashTable 欄位// gdb 中檢查陣列
print *arr
print *(HashTable*)arr->value.ht
// 遍歷桶
set $bucket = ((HashTable*)arr->value.ht)->pListHead
while ($bucket) {
print $bucket->arKey
print *(zval*)$bucket->pData
set $bucket = $bucket->pListNext
}