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. 如何使用css来让图片居中不变形 微信小程序和web端适用

    图片变形很多人祭奠出了妖魔鬼怪般的各种大法,比如使用jq来写,或者使用css表达式来写.今天我总结的是使用css3来写,唯一最大缺点就是对一些浏览器版本不够兼容.下面就是关于如何使用css来让图片居中 ...

  2. 乞丐版servlet容器第3篇

    4 EventListener接口 让我们继续看SocketConnector中的acceptConnect方法: @Override protected void acceptConnect() t ...

  3. c++ => new/delete

    new的具体使用方式如下: 类型 *变量名 = new 类型; delete 变量 / delete[] 变量; 类型包括数组.结构体和类 数组申请动态内存后,要使用delete[]才能把内存清除干净 ...

  4. SQL 查找重复记录

    CREATE TABLE product( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL, Pid INT NOT NULL, Pname VARCHAR(50) ...

  5. IntelliJ IDEA 2017版 编译器使用学习笔记(一) (图文详尽版);IDE快捷键使用;IDE多行代码同时编写

    IntellJ是一款强大的编译器,那么它有很多实用的功能 一.鼠标点击减少效率,快捷键实现各种跳转 (1)项目之间的跳转 快捷键位置: 操作:首先要有两个项目,然后,在不同窗口打开:如图: 然后使用快 ...

  6. ArcGIS Desktop python Add-in Python 插件的文件结构

    如上图所示: 插件文件夹在根目录下有一个config.xml文件,这个文件保存有在向导添加的描述该插件的定制信息. 插件还有一个安装文件夹,这个文件夹的主要功能是存放Python脚本. 你可以在安装文 ...

  7. Google Map API申请

    https://code.google.com/apis/console 当然需要先有个Google账户登录. 然后需要建一个项目. 然后根据package+sha1码获取密钥key 然后就可以创建凭 ...

  8. python关键的语法

    python关键的语法 1.标准类型分类

  9. 同一台服务器配置多个tomcat服务的方法

    要在同一台服务器上配置多个tomcat服务,需要解决以下几个问题 (1) 不同的tomcat启动和关闭监听不同的端口 (2) 不同的tomcat的启动文件startup.sh 中要指定各自的CATAL ...

  10. Create Your Content and Structure

    The original page source Content is the most important aspect of any site. So let's design for the c ...