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

PHP数组到底是如何实现的?是时候看一下底层源码了

qiyuwang 2024-11-06 20:15 15 浏览 0 评论

PHP 中的数组实际上是一个有序映射。映射是一种把 values 关联到 keys 的类型。--PHP手册

数组是PHP中非常强大、灵活的一种数据类型,它的底层实现为HashTable(哈希表),除了我们熟悉的PHP用户空间的Array类型之外,内核中也随处用到哈希表,比如函数、类、常量、include文件的索引表、全局符号表等都用的HashTable存储。

数组结构

PHP中HashTable的数据结构如下:

//Bucket:散列表中存储的元素
typedef struct _Bucket {
 zval val; //存储的具体value,这里嵌入了一个zval,而不是一个指针
 zend_ulong h; //key根据times 33计算得到的哈希值,或者是数值索引编号
 zend_string *key; //存储元素的key
} Bucket;

//HashTable结构
typedef struct _zend_array HashTable;
struct _zend_array {
 zend_refcounted_h gc;
 union {
 struct {
 ZEND_ENDIAN_LOHI_4(
 zend_uchar flags,
 zend_uchar nApplyCount,
 zend_uchar nIteratorsCount,
 zend_uchar reserve)
 } v;
 uint32_t flags;
 } u;
 uint32_t nTableMask; //哈希值计算掩码,等于nTableSize的负值(nTableMask = -nTableSize)
 Bucket *arData; //存储元素数组,指向第一个Bucket
 uint32_t nNumUsed; //已用Bucket数
 uint32_t nNumOfElements; //哈希表有效元素数
 uint32_t nTableSize; //哈希表总大小,为2的n次方
 uint32_t nInternalPointer;
 zend_long nNextFreeElement; //下一个可用的数值索引,如:arr[] = 1;arr["a"] = 2;arr[] = 3; 则nNextFreeElement = 2;
 dtor_func_t pDestructor;
};

注意:nNumUsed:已用bucket数 nNumOfElements:哈希表有效元素数

两者有不同的含义,当将一个元素从哈希表删除时并不会将对应的Bucket移除,而是将Bucket存储的zval修改为IS_UNDEF,只有扩容时发现nNumOfElements与nNumUsed相差达到一定数量时才会将已删除的元素全部移除,重新构建哈希表,所以 nNumUsed >= nNumOfElements

HashTable中另外一个非常重要的值arData,这个值指向存储元素数组的第一个Bucket,插入元素时按顺序依次插入数组,比如第一个元素在arData[0]、第二个在arData[1] ... arData[nNumUsed]。PHP数组的有序性正是通过arData保证的。

所以,HashTable主要依赖arData实现元素的存储、索引。插入一个元素时先将元素按先后顺序插入Bucket数组,位置是idx,再根据key的哈希值映射到散列表中的某个位置nIndex,将idx存入这个位置;查找时先在哈希表中映射到nIndex,得到value在Bucket数组的位置idx,再从Bucket数组中取出元素。

比如:

对应的HashTable如下图所示:

哈希碰撞

哈希碰撞是指不同的key可能计算得到相同的哈希值(数值索引的哈希值直接就是数值本身),但是这些值又需要插入同一个哈希表。

出现哈希碰撞之后,又如何解决呢?一般有如下几种方法:

  1. 链表法:为每个 Hash 值建立一个单链表,当发生冲突时,将记录插入到链表中。
  2. 开放寻址法:开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1),基本思想:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。
  3. 再哈希法:当发生冲突时,使用第二个、第三个哈希函数计算地址,直到无冲突时。

扩容

哈希表可存储的value数是固定的,当空间不够用时就要进行扩容了。

PHP哈希表的大小为2^n,插入时如果容量不够则首先检查已删除元素所占比例,如果达到阈值时,则将已删除元素移除,重建索引;如果未到阈值则进行扩容操作,扩大为当前大小的2倍,将当前Bucket数组复制到新的空间,然后重建索引。

重建哈希表

当删除元素达到一定数量或扩容后就需要重建哈希表,因为value在Bucket位置移动了或哈希数组nTableSize变化了导致key与value的映射关系改变。

重建过程实际就是遍历Bucket数组中的value,然后重新计算映射值更新到哈希表。

此外,还有一个重要的处理:移除已删除的value,开始的时候我们说过,删除value时只是将value的type设置为IS_UNDEF,并没有实际从Bucket数组中删除,如果这些value一直存在那么将浪费很多空间,所以这里会把它们移除,操作的方式也比较简单:将后面未删除的value依次前移。

以上,介绍了数组的哈希结构,如有遗漏和不足欢迎大家继续补充。

相关推荐

基于Docker方式安装与部署Camunda流程引擎

1Camunda简介官网:https://docs.camunda.org/manual/7.19/installation/docker/Camunda是一个轻量级、开源且高度灵活的工作流和决策自...

宝塔Linux面板如何部署Java项目?(宝塔面板 linux)

通过宝塔面板部署Java还是很方便的,至少不需要自己输入tomcat之类的安装命令了。在部署java项目前,我还是先说下目前的系统环境,如果和我的系统环境不一样,导致部署不成功,那你可能需要去找其他资...

浪潮服务器如何用IPMI安装Linux系统

【注意事项】此处以浪潮服务器为例进行演示所需使用的软件:Chrome浏览器个人PC中需要预先安装java,推荐使用jdk-8u181-windows-x64.exe【操作步骤】1、在服务器的BIOS中...

Centos7环境Hadoop3集群搭建(hadoop集群环境搭建实验报告)

由于项目需要存储历史业务数据,经过评估数据量会达到100亿以上,在原有mongodb集群和ES集群基础上,需要搭建Hbase集群进行调研,所以首先总结一下Hadoop集群的搭建过程。一、三个节点的集群...

Hadoop高可用集群搭建及API调用(hadoop高可用原理)

NameNodeHA背景在Hadoop1中NameNode存在一个单点故障问题,如果NameNode所在的机器发生故障,整个集群就将不可用(Hadoop1中虽然有个SecorndaryNameNo...

使用Wordpress搭建一个属于自己的网站

现在开源的博客很多,但是考虑到wordpress对网站的seo做的很好,插件也多。并且全世界流量排名前1000万的网站有33.4%是用Wordpress搭建的!所以尝试用Wordpress搭建一个网站...

Centos 安装 Jenkins(centos 安装ssh)

1、Java安装查看系统是否已安装Javayumlistinstalled|grepjava...

Java教程:gitlab-使用入门(java中的git)

1导读本教程主要讲解了GitLab在项目的环境搭建和基本的使用,可以帮助大家在企业中能够自主搭建GitLab服务,并且可以GitLab中的组、权限、项目自主操作...

Dockerfile部署Java项目(docker部署java应用)

1、概述本文主要会简单介绍什么是Docker,什么是Dockerfile,如何安装Docker,Dockerfile如何编写,如何通过Dockerfile安装jar包并外置yaml文件以及如何通过do...

如何在Eclipse中搭建Zabbix源码的调试和开发环境

Zabbix是一款非常优秀的企业级软件,被设计用于对数万台服务器、虚拟机和网络设备的数百万个监控项进行实时监控。Zabbix是开放源码和免费的,这就意味着当出现bug时,我们可以很方便地通过调试源码来...

Java路径-02-Java环境配置(java环境搭建及配置教程)

1Window环境配置1.1下载...

35.Centos中安装python和web.py框架

文章目录前言1.Centos7python:2.Centos8python:3.进行下载web.py框架然后应用:4.安装好之后进行验证:5.总结:前言...

《我的世界》服务器搭建(我的世界服务器如何搭建)

1.CentOS7环境1.1更改YUM源#下载YUM源文件curl-o/etc/yum.repos.d/CentOS-Base.repohttps://mirrors.aliyun.com...

CentOS 7 升级 GCC 版本(centos7.4升级7.5)

1.GCC工具介绍GCC编译器:...

Linux安装Nginx详细教程(linux安装配置nginx)

环境准备1.因为Nginx依赖于gcc的编译环境,所以,需要安装编译环境来使Nginx能够编译起来。命令:yuminstallgcc-c++显示完毕,表示安装完成:2.Nginx的http模块需要...

取消回复欢迎 发表评论: