PHP 源代码分析 Zend HashTable详解第2/3页

 更新时间:2009年08月10日 10:51:09   作者:  
在PHP的Zend引擎中,有一个数据结构非常重要,它无处不在,是PHP数据存储的核心,各种常量、变量、函数、类、对象等都用它来组织,这个数据结构就是HashTable。

在HashTable结构中,nTableSize指定了HashTable的大小,同时它限定了HashTable中能保存Bucket的最大数量,此数越大,系统为HashTable分配的内存就越多。为了提高计算效率,系统自动会将nTableSize调整到最小一个不小于nTableSize的2的整数次方。也就是说,如果在初始化HashTable时指定一个nTableSize不是2的整数次方,系统将会自动调整nTableSize的值。即
nTableSize = 2ceil(log(nTableSize, 2)) 或 nTableSize = pow(ceil(log(nTableSize,2)))
例如,如果在初始化HashTable的时候指定nTableSize = 11,HashTable初始化程序会自动将nTableSize增大到16。
arBuckets是HashTable的关键,HashTable初始化程序会自动申请一块内存,并将其地址赋值给arBuckets,该内存大小正好能容纳nTableSize个指针。我们可以将arBuckets看作一个大小为nTableSize的数组,每个数组元素都是一个指针,用于指向实际存放数据的Bucket。当然刚开始时每个指针均为NULL。
nTableMask的值永远是nTableSize – 1,引入这个字段的主要目的是为了提高计算效率,是为了快速计算Bucket键名在arBuckets数组中的索引。
nNumberOfElements记录了HashTable当前保存的数据元素的个数。当nNumberOfElement大于nTableSize时,HashTable将自动扩展为原来的两倍大小。
nNextFreeElement记录HashTable中下一个可用于插入数据元素的arBuckets的索引。
pListHead, pListTail则分别表示Bucket双向链表的第一个和最后一个元素,这些数据元素通常是根据插入的顺序排列的。也可以通过各种排序函数对其进行重新排列。pInternalPointer则用于在遍历HashTable时记录当前遍历的位置,它是一个指针,指向当前遍历到的Bucket,初始值是pListHead。
pDestructor是一个函数指针,在HashTable的增加、修改、删除Bucket时自动调用,用于处理相关数据的清理工作。
persistent标志位指出了Bucket内存分配的方式。如果persisient为TRUE,则使用操作系统本身的内存分配函数为Bucket分配内存,否则使用PHP的内存分配函数。具体请参考PHP的内存管理。
nApplyCount与bApplyProtection结合提供了一个防止在遍历HashTable时进入递归循环时的一种机制。
inconsistent成员用于调试目的,只在PHP编译成调试版本时有效。表示HashTable的状态,状态有四种:
状态值 含义
HT_IS_DESTROYING 正在删除所有的内容,包括arBuckets本身
HT_IS_DESTROYED 已删除,包括arBuckets本身
HT_CLEANING 正在清除所有的arBuckets指向的内容,但不包括arBuckets本身
HT_OK 正常状态,各种数据完全一致
复制代码 代码如下:

typedef struct _zend_hash_key {
char *arKey; /* hash元素key名称 */
uint nKeyLength; /* hash 元素key长度 */
ulong h; /* key计算出的hash值或直接指定的数值下标 */
} zend_hash_key;

现在来看zend_hash_key结构就比较容易理解了。它通过arKey, nKeyLength, h三个字段唯一确定了HashTable中的一个元素。
根据上面对HashTable相关数据结构的解释,我们可以画出HashTable的内存结构图:

二、 Zend HashTable的实现
本节具体介绍一下PHP中HashTable的实现。以下函数均取自于zend_hash.c。只要充分理解了上述数据结构,HashTable实现的代码并不难理解。
1 HashTable初始化
HashTable提供了一个zend_hash_init宏来完成HashTable的初始化操作。实际上它是通过下面的内部函数来实现的:
复制代码 代码如下:

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)
{
uint i = 3;
Bucket **tmp;
SET_INCONSISTENT(HT_OK);
if (nSize >= 0x80000000) {
/* prevent overflow */
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) { /* 自动调整nTableSize至2的n次方 */
i++;
}
ht->nTableSize = 1 << i; /* i的最小值为3,因此HashTable大小最小为8 */
}
ht->nTableMask = ht->nTableSize - 1;
ht->pDestructor = pDestructor;
ht->arBuckets = NULL;
ht->pListHead = NULL;
ht->pListTail = NULL;
ht->nNumOfElements = 0;
ht->nNextFreeElement = 0;
ht->pInternalPointer = NULL;
ht->persistent = persistent;
ht->nApplyCount = 0;
ht->bApplyProtection = 1;
/* 根据persistent使用不同方式分配arBuckets内存,并将其所有指针初始化为NULL*/
/* Uses ecalloc() so that Bucket* == NULL */
if (persistent) {
tmp = (Bucket **) calloc(ht->nTableSize, sizeof(Bucket *));
if (!tmp) {
return FAILURE;
}
ht->arBuckets = tmp;
} else {
tmp = (Bucket **) ecalloc_rel(ht->nTableSize, sizeof(Bucket *));
if (tmp) {
ht->arBuckets = tmp;
}
}
return SUCCESS;
}

在以前的版本中,可以使用pHashFunction来指定hash函数。但现PHP已强制使用DJBX33A算法,因此实际上pHashFunction这个参数并不会用到,保留在这里只是为了与以前的代码兼容。
2 增加、插入和修改元素
向HashTable中添加一个新的元素最关键的就是要确定将这个元素插入到arBuckets数组中的哪个位置。根据上面对Bucket结构键名的解释,我们可以知道有两种方式向HashTable添加一个新的元素。第一种方法是使用字符串作为键名来插入Bucket;第二种方法是使用索引作为键名来插入Bucket。第二种方法具体又可以分为两种情况:指定索引或不指定索引,指定索引指的是强制将Bucket插入到指定的索引位置中;不指定索引则将Bucket插入到nNextFreeElement对应的索引位置中。这几种插入数据的方法实现比较类似,不同的只是定位Bucket的方法。
修改HashTable中的数据的方法与增加数据的方法也很类似。
我们先看第一种使用字符串作为键名增加或修改Bucket的方法:
复制代码 代码如下:

ZEND_API int _zend_hash_add_or_update(HashTable *ht, char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)
{
ulong h;
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht); // 调试信息输出
if (nKeyLength <= 0) {
#if ZEND_DEBUG
ZEND_PUTS("zend_hash_update: Can't put in empty key\n");
#endif
return FAILURE;
}
/* 使用hash函数计算arKey的hash值 */
h = zend_inline_hash_func(arKey, nKeyLength);
/* 将hash值和nTableMask按位与后生成该元素在arBuckets中的索引。让它和
* nTableMask按位与是保证不会产生一个使得arBuckets越界的数组下标。
*/
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex]; /* 取得相应索引对应的Bucket的指针 */
/* 检查对应的桶列中是否包含有数据元素(key, hash) */
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
if (!memcmp(p->arKey, arKey, nKeyLength)) {
if (flag & HASH_ADD) {
return FAILURE; // 对应的数据元素已存在,不能进行插入操作
}
HANDLE_BLOCK_INTERRUPTIONS();
#if ZEND_DEBUG
if (p->pData == pData) {
ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pData\n");
HANDLE_UNBLOCK_INTERRUPTIONS();
return FAILURE;
}
#endif
if (ht->pDestructor) {
/* 如果数据元素存在,对原来的数据进行析构操作 */
ht->pDestructor(p->pData);
}
/* 用新的数据来更新原来的数据 */
UPDATE_DATA(ht, p, pData, nDataSize);
if (pDest) {
*pDest = p->pData;
}
HANDLE_UNBLOCK_INTERRUPTIONS();
return SUCCESS;
}
}
p = p->pNext;
}

/* HashTable中没有key对应的数据,新增一个Bucket */
p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent);
if (!p) {
return FAILURE;
}
memcpy(p->arKey, arKey, nKeyLength);
p->nKeyLength = nKeyLength;
INIT_DATA(ht, p, pData, nDataSize);
p->h = h;
// 将Bucket加入到相应的桶列中
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
if (pDest) {
*pDest = p->pData;
}
HANDLE_BLOCK_INTERRUPTIONS();
// 将Bucket 加入到HashTable的双向链表中
CONNECT_TO_GLOBAL_DLLIST(p, ht);
ht->arBuckets[nIndex] = p;
HANDLE_UNBLOCK_INTERRUPTIONS();
ht->nNumOfElements++;
// 如果HashTable已满,重新调整HashTable的大小。
ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */
return SUCCESS;
}

相关文章

  • PHP Session 变量的使用方法详解与实例代码

    PHP Session 变量的使用方法详解与实例代码

    在php中Session经常用来验证用户注册或登录之后的验证了,下面我来总结session变量的一些常用实例与用法介绍
    2013-09-09
  • 用PHP查询搜索引擎排名位置的代码

    用PHP查询搜索引擎排名位置的代码

    使用PHP快捷查询关键字在搜索引擎中的排名位置, 原理很简单,不讲了
    2010-01-01
  • php中关于hook钩子函数底层理解

    php中关于hook钩子函数底层理解

    对"钩子"这个概念其实不熟悉,最近看到一个php框架中用到这种机制来扩展项目,所以大概来了解下。 所谓Hook机制,是从Windows编程中流行开的一种技术
    2023-01-01
  • PHP中call_user_func_array回调函数的用法示例

    PHP中call_user_func_array回调函数的用法示例

    这篇文章主要给大家介绍了PHP中call_user_func_array回调函数的用法,文中给出了详细的示例代码,相信对大家的理解和学习很有帮助,有需要的朋友们可以参考借鉴,下面来一起学习学习吧。
    2016-11-11
  • php基于socket实现SMTP发送邮件的方法

    php基于socket实现SMTP发送邮件的方法

    这篇文章主要介绍了php基于socket实现SMTP发送邮件的方法,实例分析了php采用socket实现smtp发送邮件的原理与技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • php实现数组中索引关联数据转换成json对象的方法

    php实现数组中索引关联数据转换成json对象的方法

    这篇文章主要介绍了php实现数组中索引关联数据转换成json对象的方法,基于Yii框架分析了php数组与json格式数据的转换技巧,需要的朋友可以参考下
    2015-07-07
  • PHP高并发和大流量解决方案整理

    PHP高并发和大流量解决方案整理

    在本篇文章里小编给大家分享的是一篇关于PHP高并发和大流量解决方案内容,有兴趣的朋友们可以参考下。
    2019-12-12
  • PHP生成图表pChart的示例解析

    PHP生成图表pChart的示例解析

    这篇文章主要介绍了PHP生成图表pChart的示例解析,文中通过解析pChartd 工作流程和不同图表的代码展示,讲解的非常详细,对大家的学习或工作具有一定的参考价值,有需要的来和小编一起学习把吧
    2020-07-07
  • PHP实现变色验证码实例

    PHP实现变色验证码实例

    验证码想必大家都有见到过吧,在本文为大家介绍下PHP如何实现变色验证码,感兴趣的朋友可以参考下
    2014-01-01
  • PHP文件与目录操作示例

    PHP文件与目录操作示例

    这篇文章主要介绍了PHP文件与目录操作,涉及php针对文件与目录的遍历、判断与排序相关操作技巧,注释中备有较为详细的说明,需要的朋友可以参考下
    2016-12-12

最新评论