php7-hashtable总结

来源:互联网 发布:全职高手之烽火知韩 编辑:程序博客网 时间:2024/04/28 18:47

hashtable 结构

typedef struct _Hashtable {    unit32_t nTableSize   //最小为8,标准为大于实际元素个数的2次方,6->8,9->16    unit32_t nTableMask;  //tablesize-1    unit32_t nNumUsed;    //已使用的元素,当满了的时候会重构,去除已经unset的元素    unit32_t nNumOfElement;   //实际存的个数    zend_long nNextFreeElement;  //是下一个可以使用的数字键值,当你使用$array[] = xyz是被使用到。    Bucket     *arData;  //实际指向的数组,保存了所有的bukets数组元素    uint32_t   *arHash   //hash数组中查找索引为idx = ht->arHash[hash & ht->nTableMask]        dtor_func_t       pDestructor;        uint32_t          nInternalPointer;        union {            struct {                ZEND_ENDIAN_LOHI_3(                    zend_uchar    flags,                    zend_uchar    nApplyCount,                    uint16_t      reserve)            } v;            uint32_t flags;        } u;} HashTable;

bucket结构

typedef struct _Bucket {    zend_ulong    h; //索引值    zend_string   *key  //字符串键    zval    val     //zavl结构内嵌进bucket,因此不用再申请内存}  Bucket;

指针变量arData实际指向数组的头地址,数组在php7是被分配到了一块连续的内存中。arData数组以插入的顺序保存元素。所以第一个数组元素会保存在arData[0],第二元素在arData[1]等等。这跟元素对应的键没有任何关系,这只跟插入的顺序相关。

常见问题

1.Buckets不用再单独分配什么意思?

因为老的hashtable实现中,数组arData中存的是一个指向随机地址的Bucket结构体的指针,而新的实现是直接将bucket存入数组值中,也就是说之前arData[0]中只是存的bucket的地址,现在直接存bucket了。


2.Zvals不再单独分配,它会被直接嵌入到任何需要存放它的地方(例如,一个hashtable bucket中)。

同样的意思,之前bucket中是存的指向zval的一个指针,现在是直接将zval变量存进来,如果zval存的是简单类型的话,那就更方便了。


3.冲突处理链表现在用的是一个索引列表,并且每个索引是嵌入到zval中的

当bucket中的h和*key与实际查找的不对应时,那么应该是产生冲突了,新的索引列表是存在zval结构体中的u2联合体中,

    union {        uint32_t var_flags;        uint32_t next;       /* hash collision chain */        uint32_t cache_slot; /* literal cache slot */        uint32_t lineno;     /* line number (for ast nodes) */    } u2;

可以通过idx = Z_NEXT(b->val);查找出当前zval的下一个zval(处理冲突)

源码理解

解决冲突的代码如下:

zend_ulong h = zend_string_hash_val(key);unit32_t idx = ht->arHash[h & ht->nTableMask];while (idx != INVALID_IDX) {    Bucket *b = &ht->arData[idx];    if(b->h = h && zend_string_equals(b->key,key)){    return b;    }    idx = Z_NEXT(b->val);}return NULL;

遍历数组的代码如下:

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

总结(部分摘自网络)

1.arData数组以插入的顺序保存元素。所以第一个数组元素会保存arData[0],第二元素在arData[1]等等。这跟元素对应的键没有任何关系,这只跟插入的顺序相关。所以如果你在hashtable中保存了5个元素,arData[0]到arData[4]的槽(slot)会被用到,下一个空闲槽是arData[5]。这个数字会记录在nNumUsed中,它们是一样的,不过仅仅只是在只执行插入操作的情况下。如果一个元素被从Hashtable中删除,我们肯定不想把arData数组中位于删除元素后面的元素都前移一位从而保持数组的连续性,相反我们只是将删除元素zval类型标记为IS_UNDEF。

2.实际上Hashtable的查找还是使用了arHash这个数组,这个数组由unit32_t类型的值组成。arHash数组跟arData的大小一样(都是nTableSize),并且都被分配了一段连续的内存区块。在hash数组中查找索引为idx = ht->arHash[hash & ht->nTableMask]的元素。这个索引对应的元素是冲突处理链表的头元素。所以ht->arData[idx]是我们要检查的第一个元素。如果这个元素中保存的键跟我们要查找的键相同,那么查找就搞定了。如果不的相同的话,则需要查找冲突处理链表的下一个元素,这个元素的索引保存在bucket->val.u2.next中,它保存在zval结构体中很少会用到的最后4个字节中。我们继续遍历这个链表(使用索引而不是指针)直到找到我们要找的bucket,或者是碰到INVALID_IDX——这意味着你要查找的key并不存在。