百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

PHP 中数组是如何灵活支持多数据类型的?

wptr33 2025-09-01 15:49 9 浏览

hello,大家好,我是张张,「架构精进之路」公号作者。


在PHP中,数组数据结构的应用处理是使用频率非常高的,相对于Java、C++ 这种强类型语言来说,PHP的数组简直可以说是太好用了,可以存储各种类型的数据(如:数字、字符串甚至对象等),为开发带来了极大的便利。

基于 PHP 数组的强大特性,我们可以轻易实现更加复杂的数据结构,比如栈、队列、列表、集合、字典等。

你是否迫不及待的想要一探究竟:PHP到底是如何实现数组的呢?


1、PHP数组底层数据结构

PHP 数组其内部是使用 HashTable 结构来实现的,那就先来简单说说HashTable吧!

HashTable又称散列表,是通过key-value的方式来高效地访问数据的一种结构。哈希表是数组和链表的一种合并,集成了数组的寻址快,链表的插入快的特点于一身。

HashTable主要分为两个环节:

1. 哈希函数:哈希函数将要查找的值转换成数字索引,通过数字索引可以快速的找到值存在的位置。

2. 哈希碰撞:理想情况下,不同的值通过哈希函数后,出来的结果是不一样的;如果不一样的值,哈希后出来一样的数字,我们称之为哈希碰撞。

因此应用 HashTable 就必须要面临解决哈希碰撞的问题,主要的解法有两种:链表法,开放寻址法。

在zend_type.h文件中,可以找到 HashTable 的主要结构定义如下:

zend_数组 类型

挑选几个重点成员介绍一下:

  • gc: 引用计数,垃圾回收使用。
  • arData:散列表中保存存储元素的数组,其内存是连续的,arData指向数组的起始位置;
  • nTableSize:数组的总容量,即可以容纳的元素数,arData 的内存大小就是根据这个值确定的,它的大小的是2的幂次方,最小为8,然后按照 8、16、32...依次递增;

Bucket 类型

Bucket 的结构比较简单,主要用来保存元素的 key 和 value,以及一个整型的 h(散列值,或者叫哈希值)。

  • 如果元素是数值索引,则其值就是数值索引的值;
  • 如果是字符串索引,那么其值就是 key 通过 Time33 算法计算得到的散列值。

h 的值用来最终映射元素的存储位置。


2、PHP 数组的基本实现

上面部分我们了解了 zend_数组 的数据结构,那接着看看数组的初始化吧:

数组的初始化主要是针对 HashTable 成员的设置,初始化时并不会立即分配 arData 的内存,插入第一个元素之后才会分配 arData 的内存。

为了更好的理解整个hash结构,我们来举个例子说明一下这个结构:

$data = array(
    'hello' => 'haha',
    1       => 'me to'
    'world' => 'world',
    2       => 2,
);
unset($data[1]);

那上面的hash结构应该是什么样的呢?arData存储的结果应该是什么样呢?

画个图例来看看吧,更直观一些:

arData是Bucket类型的指针,用来具体存储每个元素的key,value,按照插入元素的顺序存储数据的,所以数组的顺序也是靠这个来保证。

每个arData数组的元素,从图中可以看到,左边负数是哈希值取模后的值,存储的是右边arData的索引;如-8冲突了,则存储了链表的头元素。

arData[0]: key='hello',h=xx(具体某个值),val = 'haha'

arData[1]: val是 type=IS_UNDEF 的zval(被unset后,不是立即被删除,而是置成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 底层的散列表在散列函数与元素数组之间加了一层映射表,这个映射表也是一个数组,大小和存储元素的数组相同,存储元素的类型为整型,用于保存元素在实际存储的有序数组中的下标 —— 元素按照先后顺序依次插入实际存储数组,然后将其数组下标按照散列函数散列出来的位置存储在新加的映射表中:

这样,就可以完成最终存储数据的有序性了。

PHP 数组底层结构中并没有显式标识这个中间映射表,而是与 arData 放到了一起,在数组初始化的时候并不仅仅分配用于存储 Bucket 的内存,还会分配相同数量的 uint32_t 大小的空间,这两块空间是一起分配的,然后将 arData 偏移到存储元素数组的位置,而这个中间映射表就可以通过 arData 向前访问到。

总结

PHP中的数组其特点就是将 values 映射到 keys 的类型。与其他语言不同的是,PHP中数组的 key 可以是字符串,而values可以是任意类型。

除常规增删改查之外,数组还有很多其他操作,比如复制、合并、销毁、重置等,这些操作对应的代码都位于 zend_hash.c 中,感兴趣的同学可以去了解一下。


·END·


希望今天的讲解对大家有所帮助,谢谢!

Thanks for reading!

作者:张张,十年研发风雨路,大厂架构师,「架构精进之路」专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。

关注并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。

相关推荐

如何在Linux系统中安装Docker?_如何在Linux系统中安装软件

在这篇博客中,我将引导您通过简单的步骤完成安装Docker的过程,安装docker只是小菜一碟,你只需要运行几条命令就大功告成了!...

我用Docker安装FastDFS,再也不用头疼那些错误提示了

在这里插入图片描述FastDFS的安装我们还是通过Docker来安装实现吧,直接在Linux上还装还是比较繁琐的,但就学习而言Docker安装还是非常高效的。Docker环境请自行安装哦,不清楚的...

01背包问题的js解决方式_背包算法java

如果你有兴趣看这个相信你已经对背包问题有所了解,所以关于背包问题的描述,我就不写了。...

净现值函数_净现值函数名词解释

此页面特定于Office2010的VisualBasicforApplications(VBA)语言参考。返回一个Double,指定基于一系列定期现金流(付款和收款)和贴现率的投资的...

Excel 数据分组双利器:GROUPBY 与 FREQUENCY 函数详解

这是一篇关于Excel中GROUPBY和FREQUENCY函数的详细教学教程。这两个函数都用于数据分组统计,但它们的应用场景、功能和用法有显著不同。第一部分:强大的新函数——GROUP...

熬夜7天,我总结了JavaScript与ES的25个知识点

前言说起JavaScript,大家都知道是一门脚本语言。那么ES是什么鬼呢?ES全称ECMAScript,是JavaScript语言的国际标准。最近,我总结了25条JavaScript的基础特性相关...

傻傻分不清楚的点积与矩阵乘法 Part3

作者:MinkyungKang...

Python中的数据导入与查询_python如何导入数据文件

适用场景...

10个JavaScript一行代码,解决90%的开发难题

在JavaScript开发过程中,我们经常会遇到一些看似复杂但实际上可以通过简洁的代码解决的问题。下面分享10个JavaScript一行代码技巧,解决日常开发中的常见难题。...

提高 PHP 代码质量的 36 计_php代码调试心得

1.不要使用相对路径常常会看到:require_once('../../lib/some_class.php');该方法有很多缺点:...

PHP替换字符串关键词长词优先函数

如何实现phpstr_replace替换关键词,如何控制长词优先,也不难,我就写了个这样的函数。functionmyreplace($string,$replaces){...

PHP 中数组是如何灵活支持多数据类型的?

hello,大家好,我是张张,「架构精进之路」公号作者。...

3分钟短文 | PHP判断null,别再 == 了,你真控制不住

引言PHP程序中很多地方会用到判断是否为空,比如字符串为空,数组为空,对象为空,或者其他数据类型为默认空值。今天我们说一下判断null的两种方法的区别。一般可以使用is_null函数,判断变...

C#基础:ref 参数_c# ref和out参数的区别

例在下面,我们定义了ref方法的语法。ref方法具有retrun类型,例如int、float或string,以及一个methodName,它可以是方法的任何合适名称,我们定义了参数...

「C#.NET 拾遗补漏」05:操作符的几个骚操作

阅读本文大概需要1分钟。大家好,这是极客精神【C#.NET拾遗补漏】专辑的第5篇文章,今天要讲的内容是操作符。操作符的英文是Operator,在数值计算中习惯性的被叫作运算符,所以在中文的...