PHP 源代码分析 Zend HashTable详解
前端之家收集整理的这篇文章主要介绍了
PHP 源代码分析 Zend HashTable详解,
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
HashTable在通常的数据结构教材中也称作散列表,哈希表。其基本原理比较简单(如果你对其不熟悉,请查阅随便一本数据结构教材或在网上搜索),但PHP的实现有其独特的地方。理解了HashTable的数据存储结构,对我们分析PHP的源代码,特别是Zend Engine中的虚拟机的实现时,有很重要的帮助。它可以帮助我们在大脑中模拟一个完整的虚拟机的形象。它也是PHP中其它一些数据结构如数组实现的基础。
Zend HashTable的实现结合了双向链表和向量(数组)两种数据结构的优点,为PHP提供了非常高效的数据存储和查询机制。
Let's begin!
一、 HashTable的数据结构
在Zend Engine中的HashTable的实现代码主要包括zend_hash.h,zend_hash.c这两个文件中。Zend HashTable包括两个主要的数据结构,其一是Bucket(桶)结构,另一个是HashTable结构。Bucket结构是用于保存数据的容器,而HashTable结构则提供了对所有这些Bucket(或桶列)进行管理的机制。
<div class="codetitle"><a style="CURSOR: pointer" data="40978" class="copybut" id="copybut40978" onclick="doCopy('code40978')"> 代码如下:
<div class="codebody" id="code40978">
typedef struct bucket {
ulong h; /
Used for numeric indexing /
uint nKeyLength; /
key 长度 /
void
pData; / 指向Bucket中保存的数据的指针
/
void pDataPtr; /
指针数据 /
struct bucket
pListNext; / 指向HashTable桶列中下一个元素
/
struct bucket pListLast; /
指向HashTable桶列中前一个元素 /
struct bucket
pNext; / 指向具有同一个hash值的桶列的后一个元素
/
struct bucket pLast; /
指向具有同一个hash值的桶列的前一个元素 /
char arKey[1]; /
必须是最后一个成员,key名称/
} Bucket;
在Zend HashTable中,每个数据元素(Bucket)有一个键名(key),它在整个HashTable中是唯一的,不能重复。根据键名可以唯一确定HashTable中的数据元素。键名有两种表示方式。第一种方式使用字符串arKey作为键名,该字符串的长度为nKeyLength。注意到在上面的数据结构中arKey虽然只是一个长度为1的字符数组,但它并不意味着key只能是一个字符。实际上Bucket是一个可变长的结构体,由于arKey是Bucket的最后一个成员变量,通过arKey与nKeyLength结合可确定一个长度为nKeyLength的key。这是C语言编程中的一个比较常用的技巧。另一种键名的表示方式是索引方式,这时nKeyLength总是0,长整型字段h就表示该数据元素的键名。简单的来说,即如果nKeyLength=0,则键名为h;否则键名为arKey,键名的长度为nKeyLength。
当nKeyLength > 0时,并不表示这时的h值就没有意义。事实上,此时它保存的是arKey对应的hash值。不管hash
函数怎么设计,冲突都是不可避免的,也就是说不同的arKey可能有相同的hash值。具有相同hash值的Bucket保存在HashTable的arBuckets数组(参考下面的解释)的同一个索引对应的桶列中。这个桶列是一个双向链表,其前向元素,后向元素分别用pLast,pNext来表示。新插入的Bucket放在该桶列的最前面。
在Bucket中,实际的数据是保存在pData指针指向的内存块中,通常这个内存块是系统另外分配的。但有一种情况例外,就是当Bucket保存的数据是一个指针时,HashTable将不会另外请求系统分配空间来保存这个指针,而是直接将该指针保存到pDataPtr中,然后再将pData指向本结构成员的地址。这样可以提高效率,减少内存碎片。由此我们可以看到
PHP HashTable设计的精妙之处。如果Bucket中的数据不是一个指针,pDataPtr为NULL。
HashTable中所有的Bucket通过pListNext,pListLast构成了一个双向链表。最新插入的Bucket放在这个双向链表的最后。
注意在一般情况下,Bucket并不能提供它所存储的数据大小的信息。所以在
PHP的实现中,Bucket中保存的数据必须具有管理自身大小的能力。
<div class="codetitle">
<a style="CURSOR: pointer" data="32692" class="copybut" id="copybut32692" onclick="doCopy('code32692')"> 代码如下: <div class="codebody" id="code32692">
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket
pInternalPointer;
Bucket pListHead;
Bucket *pListTail;
Bucket
arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} 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 正常状态,各种数据完全一致
<div class="codetitle">
<a style="CURSOR: pointer" data="10666" class="copybut" id="copybut10666" onclick="doCopy('code10666')"> 代码如下: