PHP 中变量名→zval, 变量值→zend_value. 其变量内存是通过引用计数管理的, 在 php7 中引用计数在 value 结构中.
变量类型:
头文件在 PHP 源码 /zend/zend_types.h
内部实现:
PHP 通过 zval 这个结构体来表示一个变量, 而不同类型的变量值则通过 zval 嵌入的一个人联合体表示, 即 zend_value.
zend_value 是一个联合体, 其代码如下:
ast,ptr,zv 这些类型只给内核自己使用.
字符串:
PHP 为字符串单独定义了一个结构: zend_string. 在 zend_value 中通过 str 指向具体结构.
存储字符串内容的 val 比较特殊.
val 并没有使用 char * 类型, 字符串分配时是类似这样操作的: malloc(sizeof(zend_sting)+ 字符串长度), 就是会多分配出一些内存来存储字符串内容, 这块多出来的内存起始位置就是 val.
这样做的好处可以省去一次内存分配(char*), 且更有助于内存管理.
val 中多出来的一个字节 (结构体中为 val[1] 而不是 val[0])用于存储存储字符串的最后一个字符 "\0".
比如 $a="abc", 则对应的 zend_string 内存结构如左图:
数组:
nTableMask: 这个值在散列函数根据 key 的 hash code 银蛇元素的存储为位置时用到. nTableMask = -nTableSize 或 nTableMask = ~nTableSize+1.
nNumUsed,nNumOfElements: 当删除数组元素时并不会立马从数组中删除, 而是将这个元素的类型标为 IS_UNDEF, 只有在数组容量超限, 需要扩容时才会删除.
若没有扩容, 则 nNumUsed 将一直递增, 所以其值并不是有效的元素数. nNumOfElements 则是数组中有效元素的数量, 所以 nNumOfElements ≤ nNumUsed.
Bucket 结构用力保存元素的 key 及 value. 而 h 是 hash code: 如果 key 是数值 (及数值索引) 那么它的值就是数值索引的值; 如果 key 是字符串, 那么它的值就是根据字符串 key 通过 Time33 算法计算得到的散列值. h 值用来映射元素的存储位置.
数组实现:
为了实现散列表的有序性, PHP 中的散列表在散列函数与元素数组之间加了一层映射表, 这个映射表也是数组, 大小与存储元素的数组相同.
中间映射表存储元素在实际存储的有序数组中的下标: 元素按照先后顺序依次插入实际存储数组, 然后将其数组下标按照散列函数散列出来的位置存储在新加的映射表中.
散列函数: 根据 key 映射出元素的的存储位置, 通常会以取模作为散列函数: key->h % nTableSize. 但 PHP 采用另一种方式: nIndex = key->h | nTableMask.
在 PHP 数组的结构中并没有发现这个中间映射表, 事实上, 它与 arData 放在一起. 在数组初始化时, 同时分配用于存储 Bucket 的内存和分配相同数量的 uint32_t 大小的空间. 然后将 arData 偏移到存储元素数组的位置.
中间映射表可以通过 arData 向前访问到.
哈希冲突: 不同的 key 值可能计算得到相同的哈希值, 在插入散列表时会发生冲突, 因为映射表只能存储一个元素.
解决方法: 把冲突的 Bucket 串成链表, 即中间映射表映射出来的是一个 Bucket 链表, 而不是一个 Bucket, 查找时需要遍历这个链表, 逐个比较 key, 从而找到目标元素.
HashTable 会记录与它冲突的元素在 arData 数组中的存储位置.
在设置映射值时, 发现中间映射表中要设置的位置已经被之前插入的元素占用了(值不等于初始化的 - 1), 那么会把已经存在的值保存到新插入的 Bucket 中(即 c 插入后 u2.next=0), 然后将映射表中的值更新为新 Bucket 的存储位置(即映射表中的值: 2).
引用:
引用是一种指向其他类型的结构, 类似 C 语言中指针的概念. 当修改引用类型的变量时, 其修改将反应到实际引用的变量上.
在 PHP 中通过 & 操作符生成一个引用变量, 比如 $b = &$a, 执行时首先为 & 操作的变量分配一个 zend_reference 结构, 这个结构就是引用类型的结构体, 它内嵌了一个 zval, 此 zval 的 value 指向原来 zval 的 value, 然后将原 zval 的类型修改为 IS_REFERENCE, 原 zval 的 value 指向新创建的 zend_reference 结构.
例子:
- $a = date("Y-m");
- $b = &$a;
$a 为字符串, 通过 &$a 将其转化为引用类型并赋值给了 $b, 转换后的 $a 的类型由 IS_STRING 变为 IS_REFERENCE,$a 的 value 也转变为 zend_reference 结构, 这个结构指向原来的字符串.
$a,$b 间接指向了实际的 value 值.
使用引用时需要注意, 引用只能通过 & 产生, 不能通过赋值传递.
如上面的例子, 再把 $b 赋值给其他变量, 那么传递给新变量的 value 将是实际引用的值, 而不是引用本身.
- $a = date("Y-m");
- $b = &$a;
- $c = $b; // 如果想让 $c 也引用指向 $a/$b 引用的值, 则:$c = &$b
来源: https://www.cnblogs.com/jiangml/p/10478132.html