http://ju.outofmemory.cn/entry/197064
http://www.fzb.me/2015-9-16-php7-implementation-hashtable.html
http://ju.outofmemory.cn/entry/154095
http://www.laruence.com/2009/08/23/1065.html
https://github.com/laruence/php7-internal/blob/master/zval.md
https://github.com/laruence/php7-internal/blob/master/zval.md
https://github.com/laruence/php7-internal/blob/master/zval.md
https://github.com/laruence/php7-internal/blob/master/zval.md
http://coolshell.cn/articles/11377.html
http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.html
http://www.supmen.com/vnzdw8op3r.html
https://segmentfault.com/a/1190000004124429
http://www.csdn.net/article/2015-09-16/2825720
http://blog.jobbole.com/96689/
http://www.cnblogs.com/yanlingyin/archive/2011/12/07/2278961.html
http://blog.csdn.net/itianyi/article/details/8593391
http://www.laruence.com/2008/08/19/338.html
http://bestphper.com/archives/75
http://www.jianshu.com/p/9f1ae9840847
https://github.com/laruence/php7-internal/pull/28/files?diff=split#diff-0e5d955754f7e87c931aa5e4f669881eL44
php5数组的相关结构体
typedef struct _hashtable {
uint nTableSize;//4 哈希表中Bucket的槽的数量,初始值为8,每次resize时以2倍速度增长
uint nTableMask;//4 nTableSize-1 , 索引取值的优化
uint nNumOfElements;//4 哈希表中Bucket中当前存在的元素个数,count()函数会直接返回此值
ulong nNextFreeElement;//4 下一个数字索引的位置
Bucket *pInternalPointer; /* Used for element traversal 4*/ 当前遍历的指针(foreach比for快的原因之一) 用于元素遍历
Bucket *pListHead;//4 存储数组头元素指针
Bucket *pListTail;//4 存储数组尾元素指针
Bucket **arBuckets;//4 //指针数组,数组中每个元素都是指针 存储hash数组
dtor_func_t pDestructor;//4 在删除元素时执行的回调函数,用于资源的释放 /* persistent 指出了Bucket内存分配的方式。如果persisient为TRUE,则使用操作系统本身的内存分配函数为Bucket分配内存,否则使用PHP的内存分配函数。*/
zend_bool persistent;//1
unsigned char nApplyCount;//1 标记当前hash Bucket被递归访问的次数(防止多次递归)
zend_bool bApplyProtection;//1 标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
#if ZEND_DEBUG
int inconsistent;//4
#endif
} HashTable;
typedef struct bucket {
ulong h; /* Used for numeric indexing 4字节 */ 对char *key进行hash后的值,或者是用户指定的数字索引值/* Used for numeric indexing */
uint nKeyLength; /* The length of the key (for string keys) 4字节 字符串索引长度,如果是数字索引,则值为0 */
void *pData; /* 4字节 实际数据的存储地址,指向value,一般是用户数据的副本,如果是指针数据,则指向pDataPtr*/ //这里又是个指针,zval存放在别的地方
void *pDataPtr; /* 4字节 引用数据的存储地址,如果是指针数据,此值会指向真正的value,同时上面pData会指向此值 */
struct bucket *pListNext; /* PHP arrays are ordered. This gives the next element in that order4字节 整个哈希表的该元素的下一个元素*/
struct bucket *pListLast; /* and this gives the previous element 4字节 整个哈希表的该元素的上一个元素*/
struct bucket *pNext; /* The next element in this (doubly) linked list 4字节 同一个槽,双向链表的下一个元素的地址 */
struct bucket *pLast; /* The previous element in this (doubly) linked list 4字节 同一个槽,双向链表的上一个元素的地址*/
char arKey[1]; /* Must be last element 1字节 保存当前值所对于的key字符串,这个字段只能定义在最后,实现变长结构体*/
} Bucket;
数组的初始化
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;
//...
if (nSize >= 0x80000000) {
/* prevent overflow */
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) {
i++;
}
ht->nTableSize = 1 << i;
}
// ...
ht->nTableMask = ht->nTableSize - 1;
/* Uses ecalloc() so that Bucket* == NULL */
if (persistent) {
tmp = (Bucket **) calloc(ht->nTableSize, sizeof(Bucket *));//sizeof(Bucket *)大小为4,也就是分配ht->nTableSise个指针
if (!tmp) {
return FAILURE;
}
ht->arBuckets = tmp;
} else {
tmp = (Bucket **) ecalloc_rel(ht->nTableSize, sizeof(Bucket *));
if (tmp) {
ht->arBuckets = tmp;
}
}
return SUCCESS;
}
数组的插入、更新
ZEND_API int _zend_hash_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC)
{
//...省略变量初始化和nKeyLength <=0 的异常处理
h = zend_inline_hash_func(arKey, nKeyLength);
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex];
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();
//..省略debug输出
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;
}
p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent); //为Bucket分配内存,这时候的内存是不连续的,在print数组时,是在链表中挨个打印,内存地址是随机的,不能使用到内存的局部性
if (!p) {
return FAILURE;
}
memcpy(p->arKey, arKey, nKeyLength);
p->nKeyLength = nKeyLength;
INIT_DATA(ht, p, pData, nDataSize);
p->h = h;
CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]); //Bucket双向链表操作
if (pDest) {
*pDest = p->pData;
}
HANDLE_BLOCK_INTERRUPTIONS();
CONNECT_TO_GLOBAL_DLLIST(p, ht); // 将新的Bucket元素添加到数组的链接表的最后面
ht->arBuckets[nIndex] = p;
HANDLE_UNBLOCK_INTERRUPTIONS();
ht->nNumOfElements++;
ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* 如果此时数组的容量满了,则对其进行扩容。*/
return SUCCESS;
}
php5 bucket中的zval只是一個指針,因此還要多分配一個指針,而php7是直接在bucket中存儲zval
ht->nTableMask的大小为ht->nTableSize -1。 这里使用&操作而不是使用取模,这是因为是相对来说取模操作的消耗和按位与的操作大很多。
nTableMask的作用就是将哈希值映射到槽位所能存储的索引范围内。 例如:某个key的索引值是21, 哈希表的大小为8,则mask为7,则求与时的二进制表示为: 10101 & 111 = 101 也就是十进制的5。 因为2的整数次方-1的二进制比较特殊:后面N位的值都是1,这样比较容易能将值进行映射, 如果是普通数字进行了二进制与之后会影响哈希值的结果。那么哈希函数计算的值的平均分布就可能出现影响。
由于php7中的bucket直接存储zval
例如
mystr = estrdup("Forty Five");
add_next_index_string(return_value, mystr);
ZEND_API int add_next_index_string(zval *arg, const char *str) /* {{{ */ { zval tmp; ZVAL_STRING(&tmp, str); return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE; } #define ZVAL_STRINGL(z, s, l) do { ZVAL_NEW_STR(z, zend_string_init(s, l, 0)); } while (0) //zend_string_init 本身是从堆中分配的内存 static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent) { zend_string *ret = zend_string_alloc(len, persistent); memcpy(ZSTR_VAL(ret), str, len); ZSTR_VAL(ret)[len] = '