php 新老hashtable的实现

老版本hashtable的实现

php中hashtable的大体结构上和别的语言没什么区别,有两部分构成hashtable和bucket 和链式存储。
来看下hashtable的定义

	typedef struct _hashtable {
	     uint nTableSize;		//	大小
	     uint nTableMask;	// hashtable的掩码  大小nTableSize-1 用来计算hash值
	     uint nNumOfElements;  //  实际存储元素的大熊啊
	     ulong nNextFreeElement;   //  下一个空闲元素的位置 例如 a[] = 'test'  一定情况下快速定位
	     Bucket *pInternalPointer;  // 当前遍历的指针  foreach 比for快的原因
	     Bucket *pListHead;     //  指向第一个元素  保障了顺序遍历
	     Bucket *pListTail;      // 指向最后一个元素
	     Bucket **arBuckets;   // Bucket 数组  key:hash值  value是指向对应第一个bucket的指针
	     dtor_func_t pDestructor;   // 析构函数
	     zend_bool persistent;   // 
	     unsigned char nApplyCount;  
	     zend_bool bApplyProtection;   //  和nApplyCount一起使用 防止无限递归 解决循环引用的问题
	 #if ZEND_DEBUG
	     int inconsistent;
	 #endif
	 } HashTable;

bucket定义

	typedef struct bucket {
	    /* Used for numeric indexing */
	    ulong h;            // 对char *key进行hash后的值,数字索引的话就是索引值
	    uint nKeyLength;    // hash关键字的长度,如果数组索引为数字,此值为0
	    void *pData;        // 指向value,一般是用户数据的副本,如果是指针数据,则指向pDataPtr
	    void *pDataPtr;     //如果是指针数据,此值会指向真正的value,同时上面pData会指向此值
	    struct bucket *pListNext;   // 整个hash表的下一元素
	    struct bucket *pListLast;   // 整个哈希表该元素的上一个元素
	    struct bucket *pNext;       // 存放在同一个hash Bucket内的下一个元素
	    struct bucket *pLast;       // 同一个哈希bucket的上一个元素
	    char arKey[1];  
	    /*存储字符索引,此项必须放在最未尾,因为此处只字义了1个字节,存储的实际上是指向char *key的值,
	    这就意味着可以省去再赋值一次的消耗,而且,有时此值并不需要,所以同时还节省了空间。
	    */
	} Bucket;

hashtable的结构图
在这里插入图片描述

新版本的hashtable

与老版本的hashtable相比改动还是挺大的

  1. 老版本的元素存储是分散的,Bucket **arBuckets 里面存储的是指针 指向bucket的地址,新版的的元素存储是连续的 Bucket *arData;
  2. 老版本bucket中有4个指针 新版版中的bucket中只有一个指针,并且只有在hash碰撞的时候才会用到

少了三个指针,看下新版本的hashtable 如何做好按照插入顺序遍历和解决hash冲突

看下hashtable的定义

   typedef struct _zend_array HashTable;
   
	struct _zend_array {
		zend_refcounted_h gc;        //  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;          //  hash表的掩码 用来确定hsh
		Bucket           *arData;                    // bucket数组
		uint32_t         *arHash;              // hashtable 查找  大小为nTableMask 存放指向bucket的指针(疑问在源码定义中未看到)
		uint32_t          nNumUsed;            //  元素个数 包含已删除的元素
		uint32_t          nNumOfElements;    //  有效的元素个数
		uint32_t          nTableSize;      //  hash表的大小
		uint32_t          nInternalPointer;     
		zend_long         nNextFreeElement;
		dtor_func_t       pDestructor;
	};

bucket的定义

typedef struct _Bucket {
	zval              val;           
	zend_ulong        h;         //存的hash 值 用来寻找对比key
	zend_string      *key;           // 如果key是string 则存放key 如果是数字 则为空
} Bucket;

typedef struct _zval_struct     zval;
struct _zval_struct {
	zend_value        value;			// value 真正的结构
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)	    /* call info for EX(This) */
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                   // 重点关注这个  存放hash 冲突下一个元素的位置
		uint32_t     cache_slot;           /* literal cache slot */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     extra;                /* not further specified */
	} u2;

};

新hashtable的结构图
在这里插入图片描述
可以看出,新的hashtable bucket使用一段连续的内存,按插入顺序依次增长。连续内存比老版本的元素分散存储要快很多,但是这样会有个问题。 元素的删除,导致索引的重建。解决办法:软删除 将val标志为UNDEF 在循环取值的判断val,代码如下

     for (i = 0; i < ht->nNumUsed; ++i) {
	    Bucket *b = &ht->arData[i];
	    if (Z_ISUNDEF(b->val)) continue;
	               // do stuff with bucket
	}

但是这样造成的内存的浪费, php在数组的扩容或需要调整的时候 才真正删除这些元素, 以空间换时间,实际上我们在使用的过程中 unset元素的操作极少。

在看下如何解决hash碰撞的
解决hash 碰撞是有两个指针解决,hashtable定义中的 *arHash 和 zval定义中的里面的 next。key hash之后 hash&nTableMask -1 获得在arHash数组中的位置,判断当前是否为null, 如为null则未产生hash碰撞,更新值为当前bucket的地址,如不为null 说明当前hash已有值,产生碰撞,设置当前bucket中的zval.next值为当前hash的值,当前hash值设为当前bucket的地址。形成如上图bk5和bk2的关系。 一个单链表解决了hash碰撞

hashtable的遍历 按arData依次遍历,foreach和for的效率基本一样

hashtable的查找 key hash之后定位在arHash的位置,一次遍历对应的单链表,判断 Bucket.h 或者 bucket.key 与key是否相等

一个疑问 新版本的hashtable是内存连续的,如果元素很多,超过操作系统单次申请的最大长度,如何处理的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值