1 数组
本节我们讲一下php的数组,在php中,数组使用HashTable实现的。本节中我们先详细的介绍一下HashTable,然后再讲讲如何使用HastTable
1.1 变长结构体
所谓的变长结构体,其实是我们C语言结构体的一种特殊用法,并没有什么新奇之处。我们先来看一下变长结构体的一种通用定义方法。
typedef struct bucket {
int n;
char key[30];
char value[1];
} Bucket;
我们定义了一个结构体Bucket,我们希望用这个结构体存放学生的个人简介。其中key用来存在学生的姓名,value用来存放学生的简介。大家可能很好奇,我们的value声明了长度为1. 1个char能存多少信息呀?
其实,对于变长结构体,我们在使用的使用不能直接定义变量,例如:Bucket bucket; 您要是这样使用,value肯定存储不了多少信息。对于变长结构体,我们在使用的时候需要先声明一个变长结构体的指针,然后通过malloc函数分配函数空间,我们需要用到的空间长度是多少,我们就可以malloc多少。通用的使用方法如下:
Bucket* pBucket;
pBucket = malloc(sizeof(Bucket) + n * sizeof(char));
其中n就是你要使用value的长度。如果这样使用的话,value指向的字符串不久变长了吗!
1.2 Hashtable简介
我们先看一下HashTable的定义
struct _hashtable;
typedef struct bucket {
ulong h;//当元素是数字索引时使用
uint nKeyLength;//当使用字符串索引时,这个变量表示索引的长度,索引(字符串)保存在最后一个元素aKey
void *pData;//用来指向保存的数据,如果保存的数据是指针的话,pDataPtr就指向这个数据,pData指向pDataPtr
void *pDataPtr;
struct bucket *pListNext; //上一个元素
struct bucket *pListLast; //下一个元素
struct bucket *pNext; //指向下一个bucket的指针
struct bucket *pLast; //指向上一个bucket的指针
char arKey[1]; //必须放在最后,主要是为了实现变长结构体
} Bucket;
typedef struct _hashtable {
uint nTableSize; //哈希表的大小
uint nTableMask; //数值上等于nTableSize- 1
uint nNumOfElements; //记录了当前HashTable中保存的记录数
ulong nNextFreeElement; //指向下一个空闲的Bucket
Bucket *pInternalPointer; //这个变量用于数组反转
Bucket *pListHead; //指向Bucket的头
Bucket *pListTail; //指向Bucket的尾
Bucket **arBuckets;
dtor_func_t pDestructor; //函数指针,数组增删改查时自动调用,用于某些清理操作
zend_bool persistent; //是否持久
unsigned char nApplyCount;
zend_bool bApplyProtection; //和nApplyCount一起起作用,防止数组遍历时无限递归
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;
希望大家能好好看看上面的定义,有些东西我将出来反而会说不明白,不如大家看看代码浅显明了。PHP的数组,其实是一个带有头结点的双向链表,其中HashTable是头,Bucket存储具体的结点信息。
1.3 HashTable内部函数分析
1.3.1 宏HASH_PROTECT_RECURSION
#defineHASH_PROTECT_RECURSION(ht) \
if ((ht)->bApplyProtection) { \
if ((ht)->nApplyCount++ >= 3){ \
zend_error(E_ERROR, "Nestinglevel too deep - recursive dependency?"); \
} \
}
这个宏主要用来防止循环引用。
1.3.2 宏ZEND_HASH_IF_FULL_DO_RESIZE
#defineZEND_HASH_IF_FULL_DO_RESIZE(ht) \
if ((ht)->nNumOfElements >(ht)->nTableSize) { \
zend_hash_do_resize(ht); \
}
这个宏的作用是检查目前HashTable中的元素个数是否大于了总的HashTable的大小,如果个数大于了HashTable的大小,那么我们就重新分配空间。我们看一下zend_hash_do_resize
static int zend_hash_do_resize(HashTable *ht)
{
Bucket **t;
IS_CONSISTENT(ht);
if ((ht->nTableSize << 1) > 0) { /* Let's double the table size */
t = (Bucket**) perealloc_recoverable(ht->arBuckets,
(ht->nTableSize << 1) * sizeof(Bucket *), ht->persistent);
if (t) {
HANDLE_BLOCK_INTERRUPTIONS();
ht->arBuckets = t;
ht->nTableSize = (ht->nTableSize << 1);
ht->nTableMask = ht->nTableSize - 1;
zend_hash_rehash(ht);
HANDLE_UNBLOCK_INTERRUPTIONS();
return SUCCESS;
}
return FAILURE;
}
return SUCCESS;
}
从上面的代码中我们可以看出,HashTable在分配空间的时候,新分配的空间等于原有空间的2倍。
1.3.3 函数 _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; //HashTable的大小默认无2的3次方
Bucket **tmp;
SET_INCONSISTENT(HT_OK);
if (nSize >= 0x80000000) {
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) {
i++;
}
ht->nTableSize = 1 << i;
}
ht->nTableMask = ht->nTableSize- 1;
ht->pDestructor = pDestructor;