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[]; /* 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 = ;
//...
if (nSize >= 0x80000000) {
/* prevent overflow */
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) {
i++;
}
ht->nTableSize = << i;
}
// ...
ht->nTableMask = ht->nTableSize - ; /* 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, )); \
} while () //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] = '\0';
return ret;
} static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent)
{
zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); GC_REFCOUNT(ret) = ; /* optimized single assignment */
GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : ) << ); zend_string_forget_hash_val(ret);
ZSTR_LEN(ret) = len;
return ret;
} #define ZVAL_NEW_STR(z, s) do { \
zval *__z = (z); \
zend_string *__s = (s); \
Z_STR_P(__z) = __s; \
Z_TYPE_INFO_P(__z) = IS_STRING_EX; \
} while () struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[];
};

php7数组结构体

typedef struct _HashTable {
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar flags,
zend_uchar nApplyCount, /* 循环遍历保护 */
uint16_t reserve)
} v;
uint32_t flags;
} u;
uint32_t nTableSize; /* hash表的大小 HashTable的大小,始终为2的指数(8,16,32,64...)。最小为8,最大值根据机器不同而不同*/
    uint32_t          nTableMask;           /* 掩码,用于根据hash值计算存储位置,永远等于nTableSize-1 */
uint32_t nNumUsed; /* arData数组已经使用的数量 */
uint32_t nNumOfElements; /* hash表中元素个数 */
uint32_t nInternalPointer; /* 用于HashTable遍历 */
zend_long nNextFreeElement; /* 下一个空闲可用位置的数字索引 */
Bucket *arData; /* 存放实际数据 */
uint32_t *arHash; /* Hash表 */
dtor_func_t pDestructor; /* 析构函数 */
} HashTable; typedef struct _Bucket {
zval val;
zend_ulong h; /* hash value (or numeric index) */
zend_string *key; /* string key or NULL for numerics */
} Bucket;

存储中,最关键的两个是两个指针*arData和*arHash。其中,arData是Bucket的实际存储位置,在HashTable初始化的时候,会分配一块连续的能连续存放nTableSize个Bucket的内存,因此在使用时可以将其当作数组访问:arData[0], arData1……;arHash是一个nTableSize大小的数组,元素的key在hash之后落在0~(nTableSize-1)之间,这个数组是arData的索引,用于根据hash值迅速找到其对应的元素。

数组初始化

ZEND_API void ZEND_FASTCALL _zend_hash_init(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
{
GC_REFCOUNT(ht) = ;
GC_TYPE_INFO(ht) = IS_ARRAY;
ht->u.flags = (persistent ? HASH_FLAG_PERSISTENT : ) | HASH_FLAG_APPLY_PROTECTION | HASH_FLAG_STATIC_KEYS;
ht->nTableSize = zend_hash_check_size(nSize);
ht->nTableMask = HT_MIN_MASK;
HT_SET_DATA_ADDR(ht, &uninitialized_bucket); //在這裏已經為ht->arData分配內存,而有些文章上說在這個函數裏,ht->arData不會被初始化
ht->nNumUsed = ;
ht->nNumOfElements = ;
ht->nInternalPointer = HT_INVALID_IDX;
ht->nNextFreeElement = ;
ht->pDestructor = pDestructor;
}
#define HT_SET_DATA_ADDR(ht, ptr) do { \
(ht)->arData = (Bucket*)(((char*)(ptr)) + HT_HASH_SIZE((ht)->
nTableMask)); \ 最少分配8个bucket
} while ()

插入,更新

static zend_always_inline zval *_zend_hash_add_or_update_i(HashTable *ht, zend_string *key, zval *pData, uint32_t flag ZEND_FILE_LINE_DC)
{
zend_ulong h;
uint32_t nIndex;
uint32_t idx;
Bucket *p; IS_CONSISTENT(ht); if (UNEXPECTED(!(ht->u.flags & HASH_FLAG_INITIALIZED))) { /* 检查hashtable是否初始化 */
CHECK_INIT(ht, );
goto add_to_hash;
} else if (ht->u.flags & HASH_FLAG_PACKED) { /* ? */
zend_hash_packed_to_hash(ht);
} else if ((flag & HASH_ADD_NEW) == ) { /* 新增 */
p = zend_hash_find_bucket(ht, key); /* 根据key查是否已经存在 */ if (p) { /* 当前的key已经存在 */
zval *data; if (flag & HASH_ADD) { /* key已经存在产生添加冲突,退出 */
return NULL;
}
ZEND_ASSERT(&p->val != pData); /* key存在的情况下,值不一样做更新操作 */
data = &p->val;
if ((flag & HASH_UPDATE_INDIRECT) && Z_TYPE_P(data) == IS_INDIRECT) {
data = Z_INDIRECT_P(data);
}
HANDLE_BLOCK_INTERRUPTIONS();
if (ht->pDestructor) {
ht->pDestructor(data); /* 释放掉原来的data */
}
ZVAL_COPY_VALUE(data, pData); /* 将新的pData值复制给原来的data */
HANDLE_UNBLOCK_INTERRUPTIONS();
return data;
}
} ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */ add_to_hash:
HANDLE_BLOCK_INTERRUPTIONS();
idx = ht->nNumUsed++; /* 已使用计数+1,并且用老的位置来做为索引 */
ht->nNumOfElements++; /* 元素个数加1 */
if (ht->nInternalPointer == INVALID_IDX) {
ht->nInternalPointer = idx;
}
p = ht->arData + idx; /* 指针加法移位 */
p->h = h = zend_string_hash_val(key); /* 计算key的hash值 */
p->key = key;
zend_string_addref(key);
ZVAL_COPY_VALUE(&p->val, pData);
nIndex = h & ht->nTableMask; /* 与tablemask进行计算得出hash索引 */
Z_NEXT(p->val) = ht->arHash[nIndex]; /* 新的元素的hash冲突链表的next指向当前冲突链表的首部元素 */
ht->arHash[nIndex] = idx; /* 新的元素放到当前hash冲突链表的头部 */
HANDLE_UNBLOCK_INTERRUPTIONS(); return &p->val;
} #define Z_NEXT(zval) (zval).u2.next

php计算hash

static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength)
{
register ulong hash = ; /* variant with the hash unrolled eight times */
for (; nKeyLength >= ; nKeyLength -= ) {
hash = ((hash << ) + hash) + *arKey++;
hash = ((hash << ) + hash) + *arKey++;
hash = ((hash << ) + hash) + *arKey++;
hash = ((hash << ) + hash) + *arKey++;
hash = ((hash << ) + hash) + *arKey++;
hash = ((hash << ) + hash) + *arKey++;
hash = ((hash << ) + hash) + *arKey++;
hash = ((hash << ) + hash) + *arKey++;
}
switch (nKeyLength) {
case : hash = ((hash << ) + hash) + *arKey++; /* fallthrough... */
case : hash = ((hash << ) + hash) + *arKey++; /* fallthrough... */
case : hash = ((hash << ) + hash) + *arKey++; /* fallthrough... */
case : hash = ((hash << ) + hash) + *arKey++; /* fallthrough... */
case : hash = ((hash << ) + hash) + *arKey++; /* fallthrough... */
case : hash = ((hash << ) + hash) + *arKey++; /* fallthrough... */
case : hash = ((hash << ) + hash) + *arKey++; break;
case : break;
EMPTY_SWITCH_DEFAULT_CASE()
}
return hash;
}

相对于apache等其他软件使用的time33算法而言,PHP并没有直接乘33,而是使用的hash << 5 + hash,这样比乘法速度更快。从这个函数可以看出来PHP鼓励hash字符串的长度小于等于8位,一般也不会有人把key的长度设置的超过8位吧。说白了就是以空间换时间,哈希的字符串长度大于8位时一次for循环就执行了8次hash

hash的初始值设置成了5381, 相比在Apache中的times算法和Perl中的Hash算法(都采用初始hash为0), 为什么是5381?
这是个神奇的数字,集素数、奇数、缺数为一身,而且它的二进制也很独特。在测试中,5381可以导致哈希碰撞更少,避免雪崩。

case后面的常量表达式实际上只起语句标号作用,而不起条件判断作用,即"只是开始执行处的入口标号". 因此,一旦与switch后面圆括号中表达式的值匹配,就从此标号处开始执行,而且执行完一个case后面的语句后,若没遇到break语句,就自动进入 下一个case继续执行,而不在判断是否与之匹配,直到遇到break语句才停止执行,退出break语句.因此,若想执行一个case分之后立即跳出 switch语句,就必须在此分支的最后添加一个break语句.

HashTable的大小,始终为2的指数(8,16,32,64...)。最小为8,最大值根据机器不同而不同

php5数组与php7数组区别的更多相关文章

  1. PHP5.6 和PHP7.0区别

    1. PHP7.0 比PHP5.6性能提升了两倍. 2.PHP7.0全面一致支持64位. 3.PHP7.0之前出现的致命错误,都改成了抛出异常. 4.增加了空结合操作符(??).效果相当于三元运算符. ...

  2. Java中长度为0的数组与null的区别

    有如下两个变量定义,这两种定义有什么区别呢? 1. int[] zero = new int[0]; 2. int[] nil = null; zero是一个长度为0的数组,我们称之为“空数组”,空数 ...

  3. (实用篇)PHP中unset,array_splice删除数组中元素的区别

    php中删除数组元素是非常的简单的,但有时删除数组需要对索引进行一些排序要求我们会使用到相关的函数,这里我们来介绍使用unset,array_splice删除数组中的元素区别吧 如果要在某个数组中删除 ...

  4. (转载)C语言 数组与指针的区别

    1) 字符串指针变量是个变量,指向字符串的首地址:而字符串数组名是个常量,为字符串数组第一个元素的地址: 2)字符串指针变量可以赋值,而字符串数组名不能赋值:对于字符数组只能对各个元素赋值,不能用以下 ...

  5. js 数组与对象的区别

    学习javascript的时候,我曾经一度搞不清楚”数组”(array)和”对象”(object)的根本区别在哪里,两者都可以用来表示数据的集合.   比如有一个数组a=[1,2,3,4],还有一个对 ...

  6. 面试之路(8)-BAT面试题之数组和链表的区别

    两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用 各自的特点: 数组: 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素.但是如果要在数组中增加一个 ...

  7. js注意点:数组比较大小方法及数组与对象的区别

    (迁移自旧博客2017-04-19) 快速复制数组及数组比较大小方法 首先介绍一下复制数组的方法: var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; var ...

  8. 字符类型char、字符串与字符数组、字符数组与数据数组区别

    字符类型是以ASCII码值运算的:小写字母比相应的大写字母大32,其中A=65,a=97 Esc键 27(十进制).'\x1B'(十六进制).'\33'(八进制) 转义字符:\0 空字符     AS ...

  9. String.getBytes()和String.tocharArray(),字节数组和字符数组的区别

    String.getBytes()是将字符串转化为一个字节数组.而String.toCharArray()是将一个字符串转化为一个字符数组. [例如] byte bys[] ="国庆60周年 ...

随机推荐

  1. 【JS】判断浏览器类型

    判断原理 JavaScript是前端开发的主要语言,我们可以通过 编写JavaScript程序来判断浏览器的类型及版本.JavaScript判断浏览器类型一般有两种办法,一种是根据各种浏览器独有的属性 ...

  2. 项目管理心得:一个项目经理的个人体会、经验总结(zz)

    本人做项目经理工作多年,感到做这个工作最要紧的就是要明白什么是因地制宜.因势利导,只有最合适的,没有什么叫对的,什么叫错的,项目经理最忌讳 的就是完美主义倾向,尤其是做技术人员出身的,喜欢寻找标准答案 ...

  3. 2018.07.31 bzoj4569: [Scoi2016]萌萌哒(并查集+倍增)

    传送门 对于每个限制,使用倍增的二进制拆分思想,用并查集数组fa[i][j]" role="presentation" style="position: rel ...

  4. python面向对象-3类的静态方法和类方法

    还是以上次的洗衣机例子: class Washer: company='ZBL' def __init__(self,water=10,scour=2): self._water=water #不想让 ...

  5. php读取用友u8客户档案

    include('../common/conn.php'); $list=[]; $sql="SELECT a.cCusCode,a.cCusName,b.cCCName,a.cCusDep ...

  6. faceswap requirements

    tqdm psutil pathlib==1.0.1 scandir==1.7 opencv-python scikit-image scikit-learn matplotlib==2.2.2 ff ...

  7. Oracle实现递归查询

    前几天在开发的过程中遇到一个递归查询的问题,java代码大致是这样的: // 递归得到四级机构对象 public UserManagerDept getuserManagerDeptBy(String ...

  8. codevs 1083

    这道题是看了人家大牛的解题报告: 对了,要说明一下,(A+B)&1 ,表示,判断(A+B)是奇数否? 下面给出代码: #include<iostream> #include< ...

  9. 从数据库到NoSQL思路整理

    1. 数据库为什么要算范式?细说起来太多. 范式解决了数据冗余,从而保证ACID的操作性能.不然一堆删除异常,插入异常,就没法愉快的写SQL了 另外,对于多个业务公用的数据库,范式解决了集成的问题. ...

  10. node.js+express+mongodb

    主要是想用node.js链接mongodb,用的是mongoose.用ejs引擎,扩展到.html比较容易 小例子结构简单,框架清晰. 提交方法 路径 方法 作用 get add     post a ...