Zend 哈希表的内部实现

http://www.nowamagic.net/academy/detail/1201006

数据结构

PHP中使用一个叫Bucket的结构体表示桶(桶的相关参考Linux内核中的hash与bucket
),同一哈希值的所有桶被组织为一个单链表。哈希表使用HashTable结构体表示。相关源码在zend/Zend_hash.h下:

01 typedef struct bucket {
02     /* Used for numeric indexing */
03     ulong h;            // 对char *key进行hash后的值,或者是用户指定的数字索引值
04     uint nKeyLength;    // hash关键字的长度,如果数组索引为数字,此值为0
05     void *pData;        // 指向value,一般是用户数据的副本,如果是指针数据,则指向pDataPtr
06     void *pDataPtr;     //如果是指针数据,此值会指向真正的value,同时上面pData会指向此值
07     struct bucket *pListNext;   // 整个hash表的下一元素
08     struct bucket *pListLast;   // 整个哈希表该元素的上一个元素
09     struct bucket *pNext;       // 存放在同一个hash Bucket内的下一个元素
10     struct bucket *pLast;       // 同一个哈希bucket的上一个元素
11     char arKey[1]; 
12     /*存储字符索引,此项必须放在最未尾,因为此处只字义了1个字节,存储的实际上是指向char *key的值,
13     这就意味着可以省去再赋值一次的消耗,而且,有时此值并不需要,所以同时还节省了空间。
14     */
15 } Bucket;
16  
17 typedef struct _hashtable {
18     uint nTableSize;        // hash Bucket的大小,最小为8,以2x增长。
19     uint nTableMask;        // nTableSize-1 , 索引取值的优化
20     uint nNumOfElements;    // hash Bucket中当前存在的元素个数,count()函数会直接返回此值
21     ulong nNextFreeElement; // 下一个数字索引的位置
22     Bucket *pInternalPointer;   // 当前遍历的指针(foreach比for快的原因之一)
23     Bucket *pListHead;          // 存储数组头元素指针
24     Bucket *pListTail;          // 存储数组尾元素指针
25     Bucket **arBuckets;         // 存储hash数组
26     dtor_func_t pDestructor;
27     zend_bool persistent;
28     unsigned char nApplyCount; // 标记当前hash Bucket被递归访问的次数(防止多次递归)
29     zend_bool bApplyProtection;// 标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
30 #if ZEND_DEBUG
31     int inconsistent;
32 #endif
33 } HashTable;
  • HashTable结构体用于保存整个哈希表需要的基本信息,而Bucket结构体用于保存具体的数据内容。他们的字段名很清楚的表明其用途,注释上面也写得比较清楚了。

重点明确下面几个字段:

  • Bucket中的“h”用于存储原始key;
  • HashTable中的nTableMask是一个掩码,一般被设为nTableSize - 1,与哈希算法有密切关系,后面讨论哈希算法时会详述;
  • arBuckets指向一个指针数组,其中每个元素是一个指向Bucket链表的头指针。
哈希算法

PHP哈希表最小容量是8(2^3),最大容量是0x80000000(2^31),并向2的整数次幂圆整(即长度会自动扩展为2的整数次幂,如13个元素的哈希表长度为16;100个元素的哈希表长度为128)。nTableMask被初始化为哈希表长度(圆整后)减1。具体代码在zend/Zend_hash.c的_zend_hash_init函数中,这里截取与本文相关的部分并加上少量注释。

01 ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
02 {
03     uint i = 3;
04     Bucket **tmp;
05  
06     SET_INCONSISTENT(HT_OK);
07  
08     //长度向2的整数次幂圆整
09     if (nSize >= 0x80000000) {
10         /* prevent overflow */
11         ht->nTableSize = 0x80000000;
12     else {
13         while ((1U << i) < nSize) {
14             i++;
15         }
16         ht->nTableSize = 1 << i;
17     }
18  
19     ht->nTableMask = ht->nTableSize - 1;
20  
21     /*此处省略若干代码…*/
22  
23     return SUCCESS;
24 }

值得一提的是PHP向2的整数次幂取圆整方法非常巧妙,可以背下来在需要的时候使用。

Zend HashTable的哈希算法比较简单:

1 hash(key)=key & nTableMask

即简单将数据的原始key与HashTable的nTableMask进行按位与即可。

如果原始key为字符串,则首先使用Times33算法将字符串转为整形再与nTableMask按位与。

1 hash(strkey)=time33(strkey) & nTableMask

下面是Zend源码中查找哈希表的代码:

01 ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void**pData)
02 {
03     uint nIndex;
04     Bucket *p;
05  
06     IS_CONSISTENT(ht);
07  
08     nIndex = h & ht->nTableMask;
09  
10     p = ht->arBuckets[nIndex];
11     while (p != NULL) {
12         if ((p->h == h) && (p->nKeyLength == 0)) {
13             *pData = p->pData;
14             return SUCCESS;
15         }
16         p = p->pNext;
17     }
18     return FAILURE;
19 }
20  
21 ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)
22 {
23     ulong h;
24     uint nIndex;
25     Bucket *p;
26  
27     IS_CONSISTENT(ht);
28  
29     h = zend_inline_hash_func(arKey, nKeyLength);
30     nIndex = h & ht->nTableMask;
31  
32     p = ht->arBuckets[nIndex];
33     while (p != NULL) {
34         if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
35             if (!memcmp(p->arKey, arKey, nKeyLength)) {
36                 *pData = p->pData;
37                 return SUCCESS;
38             }
39         }
40         p = p->pNext;
41     }
42     return FAILURE;
43 }

其中zend_hash_index_find用于查找整数key的情况,zend_hash_find用于查找字符串key。逻辑基本一致,只是字符串key会通过zend_inline_hash_func转为整数key,zend_inline_hash_func封装了times33算法,具体代码就不贴出了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值