php内核数组(HashTable)实现方式

数组是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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章