baiyanphp
所有视频:https://segmentfault.com/a/11...node
源视频地址:http://replay.xesv5.com/ll/24...redis
typedef unsigned char zend_uchar; struct _zval_struct { zend_value value; /* 存储变量的zhi*/ union { struct { ZEND_ENDIAN_LOHI_4( //大小端问题,详情看"PHP内存管理3笔记” zend_uchar type, //注意这里就是存放变量类型的地方,char类型 zend_uchar type_flags, //类型标记 zend_uchar const_flags, //是不是常量 zend_uchar reserved) //保留字段 } v; uint32_t type_info; } u1; union { uint32_t next; /* 数组模拟链表,在下文链地址法解决哈希冲突时使用 */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ uint32_t extra; /* not further specified */ } u2; };
/* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 /* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 /* fake types */ #define _IS_BOOL 13 #define IS_CALLABLE 14 #define IS_ITERABLE 19 #define IS_VOID 18 /* internal types */ #define IS_INDIRECT 15 #define IS_PTR 17 #define _IS_ERROR 20
typedef union _zend_value { zend_long lval; //存整型值 double dval; //存浮点值 zend_refcounted *counted; //存引用计数值 zend_string *str; //存字符串值 zend_array *arr; //存数组值 zend_object *obj; //存对象值 zend_resource *res; //存资源值 zend_reference *ref; //存引用值 zend_ast_ref *ast; //存抽象语法树 zval *zv; //内部使用 void *ptr; //不肯定类型,取出来以后强转 zend_class_entry *ce; //存类 zend_function *func;//存函数 struct { uint32_t w1; uint32_t w2; } ww; //这个union一共8B,这个结构体每一个字段都是4B,由于全部联合体字段共用一块内存,故至关于取了一半的union } zend_value;
<?php $a = 1; echo $a; $b = 1.1; echo $b; $c = "hello"; echo $c; $d = [1,2,3]; echo $d;
首先思考一个问题,若是让咱们本身基于C语言设计一个zend_string,应该如何设计?算法
struct _zend_string { zend_refcounted_h gc; zend_ulong h; /* 冗余的hash值 */ size_t len; char val[1]; };
struct _zend_array { zend_refcounted_h gc; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar flags, zend_uchar nApplyCount, zend_uchar nIteratorsCount, zend_uchar consistency) } v; uint32_t flags; } u; uint32_t nTableMask; Bucket *arData; //实际存储数组元素的bucket uint32_t nNumUsed; uint32_t nNumOfElements; uint32_t nTableSize; uint32_t nInternalPointer; zend_long nNextFreeElement; dtor_func_t pDestructor; }; typedef struct _zend_array HashTable;
typedef struct _Bucket { zval val; //元素的值 zend_ulong h; //冗余的哈希值 zend_string *key; //元素的key } Bucket;
nIndex = h | nTableMask
而后在slot的对应空间内存上第一个bucket对应的索引下标,而后将元素存入对应索引下标的bucket数组中。查找过程也是相似的(下面会细讲),它们都是O(1)的时间复杂度,可是这样就会出现哈希冲突,解决哈希冲突一般有两种算法:express
比较经常使用的是链地址法,但若是同一个hash值上的链表过长,会把同一个hash值上的全部链表节点都遍历一遍,时间复杂度会退化为O(n)。PHP5中有一个漏洞,攻击者不断让你的链表变长,使得数组查询变慢,不断消耗服务器性能,最终QPS会降低的很是之快。要解决链地址法的哈希冲突所带来的性能降低问题,有以下思路:segmentfault
$a['foo'] = 1; $a[] = 2; $a['s'] = 3; $a['x'] = 4;
这是一个很是简单的几个数组赋值语句,咱们具体看一下它们的插入过程:数组
- $a['foo'] = 1;这里的key和value若是是字符串,须要单独在zend_string结构中存储其真实的值和长度,这里作了简化。
- $a[] = 2;
- $a['s'] = 3; 这里注意须要先修改索引数组,保证索引数组中第一个指向的bucket数组单元是最后插入bucket数组的值(头插法),而且修改val.u2.next指针(由于全部val都是zval类型),指向上一个具备相同hash值的元素。
- $a['x'] = 4;同上
再来看一个数组查询过程,例如访问$a['s']的值:服务器
下面咱们再看一段PHP代码:数据结构
<?php for ($i = 0; $i<=200000; $i++){ $arr1[$i] = $i; } for($i = 200000; $i>=0; $i--) { $arr2[$i] = $i; }
那么为何会这样呢?先看两个概念:packed array与hash array函数
packed array的特色:
$arr = [ 0 => 0, 1 => 1, 2 => 2, 100000 => 3 ];