大家好,我是公众号“架构改进之路”的作者张章。
在PHP中,数组数据结构的应用处理是非常频繁的。 与Java和C++这些强类型语言相比,PHP字段简直太容易使用了,并且可以存储各种类型的数据。 (如:数字、字符串甚至对象等),这给开发带来了很大的便利。
基于PHP数组的强大功能,我们可以轻松实现更复杂的数据结构,例如栈、队列、列表、集合、字典等。
您是否渴望了解:PHP 是如何实现字段的?
1. PHP字段底层数据结构
PHP数组内部是使用HashTable结构实现的,所以我们先来说说HashTable!
HashTable,也称为散列表,是一种通过键值方式高效访问数据的结构。 哈希表是链表和数组的结合体,它综合了链表快速轮询和链表快速插入的特点。
HashTable主要分为两部分:
1、哈希函数:哈希函数将要查找的值转换为数字索引,通过数字索引可以快速找到该值的位置。
2、哈希碰撞:理想情况下,不同的值经过哈希函数后,结果是不同的; 如果值不相同,则相同的数字将被散列,我们称之为散列冲突。
因此php打印数组,HashTable的应用必然面临哈希冲突的问题。 主要有两种解决方案:链表法和开放寻址法。
在zend_type.h文件中,可以找到HashTable的主要结构定义如下:
zend_array 类型
选取几位主要成员介绍一下:
斗式
Bucket结构比较简单,主要用来存储元素的key和value,以及一个整数h(哈希值,或者说散列值)。
h的值作为最终map元素的存储位置。
2. PHP数组的基本实现
上面的部分我们了解了zend_数组的数据结构,那么我们来看看字段的初始化:
数组的初始化主要是对HashTable成员的设置,初始化时不会立即分配arData的显存,而是在插入第一个元素后才分配arData的显存。
为了更好的理解整个hash结构,我们举个例子来说明一下这个结构:
$data = array(
'hello' => 'haha',
1 => 'me to'
'world' => 'world',
2 => 2,
);
unset($data[1]);
之前的哈希结构应该是什么样的? 存储在 arData 中的结果应该是什么样的?
画个图例看看吧,比较直观:
arData是Bucket类型的指针,用于专门存储每个元素的key和value,并且按照元素插入的顺序存储数据,因此字段的顺序也由此得到保证。
每个arData字段的元素,从图中可以看出,左边的正数是对hash值取模后的值,它存储了左边arData的索引; 如果-8冲突,则存储数组的头元素。
arData[0]: key='hello', h=xx (具体值), val = 'haha'
arData[1]:val是一个type=IS_UNDEF的zval(取消设置后,并没有立即删除,而是设置为IS_UNDEF)
arData[2]: key='world', h=xx (具体值), val = 'world'
arData[3]: key=NULL, h=2 (可能哈希值冲突), val = 2
....
上面的例子非常具体地解释了nNumUsed、nNumOfElements、arData的含义。
3. PHP数组的有序性
数组中元素的顺序与插入顺序一致。 这是如何实现的?
为了实现PHP数组的有序性,PHP底层哈希表添加了哈希函数与元素链表之间的映射表。 这个映射表也是一个字段,其大小与存储元素的链表相同,存储元素的类型为Integer类型,用于保存元素在实际存储的有序链表中的下标——元素按顺序插入到实际存储的链表中,然后根据哈希函数对链表下标进行哈希处理,存储到新添加的映射表中:
这样就可以完成最终存储数据的排序。
PHP数组的底层结构并没有显式标记这个中间映射表,而是将其与arData放在一起。 链表初始化的时候php打印数组,不仅分配显存用于存储Bucket,还分配相同数量的uint32_t大小的空间,这两块空间一起分配,然后将arData偏移到链表所在的位置存储元素,可以通过arData向前访问这个中间映射表。
总结
PHP 中的字段通过将值映射到键的类型来表征。 与其他语言不同的是,PHP 中链表的键可以是字符串,值可以是任意类型。
除了常规的增删改查之外,对数组还有很多其他的操作,比如复制、合并、销毁、重置等,这些操作对应的代码位于zend_hash.c中,感兴趣的朋友可以去了解一下。
·结尾·
希望明天的讲解对您有所帮助,谢谢!
谢谢阅读!
作者:张章,十年风雨研发,大厂建筑师,《建筑精修之路》专注于建筑技术沉淀、职业生涯和认知升级的学习和分享,坚持分享接地气的干文章,并期待与您一起成长。