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