數組是php重要的部分,內核中也有大量使用。一起來看看是如何實現的吧。
php7中數組類型有兩個概念分爲packed、hash數組。
packed 數組:key 爲順序數字,索引數組。
hash 數組:key爲字符串,關鍵數組。
下面主要是hash數組的插入、更新、及hash 衝突時解決方法。
數組使用到的結構體_zend_array 、_Bucket 。
使用數組字符串key生成hash值
zend_string_hash_val(key)
使用h | ht->nTableMask 計算映射表的索引編號(負數)
nIndex = h | ht->nTableMask
數組元素在內存中的偏移量
idx = HT_HASH_EX(arData, nIndex);
根據偏移量找到元素
p = HT_HASH_TO_BUCKET_EX(arData, idx);
zend_array 組成部分
typedef struct _Bucket {
zval val; // 數組元素的值
zend_ulong h; // hash值或數組索引
zend_string *key; // 字符串key,數字key時爲NULL
} Bucket;
typedef struct _zend_array HashTable;
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar consistency)
} v;
uint32_t flags;
} u;
uint32_t nTableMask; // 掩碼 獲取nIndex = h | ht->nTableMask數據取值-nTableSize
Bucket *arData; // Bucket 數組
uint32_t nNumUsed; // 已使用的Bucke個數(包括標記刪除的)
uint32_t nNumOfElements; // 有效元素數
uint32_t nTableSize; // 數組空間的大小,爲2的n次方
uint32_t nInternalPointer;
zend_long nNextFreeElement; // 自增packed array 索引
dtor_func_t pDestructor; // destruct 方法
};
插入或更新數組元素
static zend_always_inline zval *_zend_hash_add_or_update_i(HashTable *ht, zend_string *key, zval *pData, uint32_t flag ZEND_FILE_LINE_DC)
{
zend_ulong h;
uint32_t nIndex;
uint32_t idx;
Bucket *p;
IS_CONSISTENT(ht);
HT_ASSERT_RC1(ht);//是否可以修改 (檢查數組refcoun是否大於1,或者是不可變數組)
if (UNEXPECTED(!(ht->u.flags & HASH_FLAG_INITIALIZED))) { // 是否分配內存空間
CHECK_INIT(ht, 0);
goto add_to_hash;
} else if (ht->u.flags & HASH_FLAG_PACKED) { // 是否是hash數組
zend_hash_packed_to_hash(ht); // 轉化成hash數組
} else if ((flag & HASH_ADD_NEW) == 0) { // 是更新還是新增
p = zend_hash_find_bucket(ht, key); //根據key查找元素是否存在
if (p) { //如果找到更新
zval *data;
if (flag & HASH_ADD) {
if (!(flag & HASH_UPDATE_INDIRECT)) {
return NULL;
}
ZEND_ASSERT(&p->val != pData);
data = &p->val;
if (Z_TYPE_P(data) == IS_INDIRECT) {
data = Z_INDIRECT_P(data);
if (Z_TYPE_P(data) != IS_UNDEF) {
return NULL;
}
} else {
return NULL;
}
} else {
ZEND_ASSERT(&p->val != pData);
data = &p->val;
if ((flag & HASH_UPDATE_INDIRECT) && Z_TYPE_P(data) == IS_INDIRECT) {
data = Z_INDIRECT_P(data);
}
}
if (ht->pDestructor) {
ht->pDestructor(data);
}
ZVAL_COPY_VALUE(data, pData);
return data;
}
}
//下在是新增
ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */
add_to_hash:
idx = ht->nNumUsed++; //元素指針的偏移量 (等於有效元素+1)
ht->nNumOfElements++; //增加有效元素個數
if (ht->nInternalPointer == HT_INVALID_IDX) {
ht->nInternalPointer = idx;
}
zend_hash_iterators_update(ht, HT_INVALID_IDX, idx);
p = ht->arData + idx; // 找到元素有存儲位置
p->key = key; // 保存key
if (!ZSTR_IS_INTERNED(key)) { //檢查key是否是內部字符串
zend_string_addref(key);
ht->u.flags &= ~HASH_FLAG_STATIC_KEYS;
zend_string_hash_val(key);
}
p->h = h = ZSTR_H(key); // 保存數組key的hash值
ZVAL_COPY_VALUE(&p->val, pData); // 保存數組的值
nIndex = h | ht->nTableMask; //計算數組映射表位置
Z_NEXT(p->val) = HT_HASH(ht, nIndex); //元素next 映射表位置
HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx); // 保存元素偏移保存在映射表
return &p->val;
}
根據字符串key查找數組元素
static zend_always_inline Bucket *zend_hash_find_bucket(const HashTable *ht, zend_string *key)
{
zend_ulong h; //計算出來的hash值
uint32_t nIndex; //映射表的索引 是個負數
uint32_t idx; //元素位置的偏移量
Bucket *p, *arData; // 元素
h = zend_string_hash_val(key); // 根據key生成hash值
arData = ht->arData; // 元素指針位置
nIndex = h | ht->nTableMask; // 計算映射表中位置(負數)
idx = HT_HASH_EX(arData, nIndex); // 根據映射表位置找到元素在數組中的偏移量
while (EXPECTED(idx != HT_INVALID_IDX)) { // 判斷映射表是否有效
p = HT_HASH_TO_BUCKET_EX(arData, idx); //根據偏移量找到數組元素位置
if (EXPECTED(p->key == key)) { /*判斷key是否相等 */
return p;
} else if (EXPECTED(p->h == h) && // hash值是否相等
EXPECTED(p->key) &&
EXPECTED(ZSTR_LEN(p->key) == ZSTR_LEN(key)) && //key長度是否相等
EXPECTED(memcmp(ZSTR_VAL(p->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0)) { // 對比hash、key的長度、key是否相等
return p;
}
idx = Z_NEXT(p->val); // 如果不相等通過(zval).u2.next找到新的偏移量(hash衝突時,會保存鏈表結構)
}
return NULL;
}