跳转到主内容
极星编程网:以代码为星,赴技术山海!

PHP数组底层:哈希表在PHP数组实现中的原理简析

PHP数组底层基于哈希表实现,通过zend_array结构、DJBX33A哈希算法、链地址法处理冲突、双向链表维持插入顺序,并在满足条件时启用packed array优化整数索引访问。

PHP数组表面是灵活的键值容器,底层却依赖哈希表实现高效存取。当您执行$arr['user'] = 'admin'或$arr[0] = 42时,引擎并非简单写入内存,而是通过哈希计算定位存储位置,并维护插入顺序。以下是哈希表在PHP数组中发挥作用的核心机制:

一、哈希表结构体与内存布局

PHP数组对应一个zend_array结构(PHP 7+),其核心为 HashTable,包含桶数组arData、哈希掩码nTableMask、元素数量nNumOfElements和容量nTableSize。arData 指向一块连续分配的内存,每个单元为 Bucket,大小固定为sizeof(Bucket)。

1、

nTableMask恒等于nTableSize - 1,且nTableSize始终为 2 的幂次,使哈希索引可通过位运算hash & nTableMask替代耗时的取模运算。

2、每个 Bucket 存储zval(值)、h(整数键或字符串哈希值)、key(字符串键指针)及next(冲突链表指针)。

立即学习“PHP免费学习笔记(深入)

”;

3、Bucket 内存按顺序连续排列,但逻辑上通过哈希索引和链表指针组织,兼顾随机访问与遍历效率。

二、键的哈希计算与槽位映射

哈希函数决定键如何映射到 arData 的物理位置。PHP 使用 DJBX33A 算法处理字符串键,生成 64 位哈希值;整数键则直接作为h字段使用,跳过哈希计算。该设计确保两类键均能快速参与索引定位。

1、对字符串键

'config',引擎调用zend_string_hash_val()获取哈希值,再与nTableMask执行按位与,得到初始槽位索引。

2、对整数键

123,直接将 123 赋给 Bucket 的h字段,并用相同位运算定位槽位。

3、若目标槽位已被占用,新 Bucket 不覆盖原数据,而是通过next字段链接至该槽位的冲突链表头部。

三、哈希冲突的链地址法处理

当多个键经哈希后落入同一槽位,PHP 采用链地址法(separate chaining),而非开放寻址法。每个槽位可承载多个 Bucket,形成以 arData[i] 为头节点的单向链表,避免探测式查找带来的性能退化和空间浪费。

1、插入冲突键时,新 Bucket 的next指向当前槽位首 Bucket,随后 arData[i] 指针更新为指向新 Bucket,实现 O(1) 头插。

PHP 8.5.5 PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。

下载2、查找时,引擎先定位 arData[i],再沿next链表逐个比对h值与key内容(字符串需 memcmp,整数键直接比较 h),确保语义一致性。

3、删除操作仅标记 Bucket 为“已删除”(via ZVAL_UNDEF),不调整链表结构,待后续插入或遍历时统一清理。

四、双向链表保障插入顺序

PHP 数组必须保持 foreach 遍历时的插入顺序,这由 Bucket 中隐式维护的双向链表实现。该链表独立于哈希索引,通过pListHead和pListTail指针管理,不依赖哈希槽位分布。

1、每次插入新元素,无论是否发生哈希冲突,该 Bucket 均被追加至双向链表尾部,pListTail->next指向新 Bucket,新 Bucket 的prev指向原尾部。

2、遍历时引擎跳过哈希表,直接从

pListHead出发,沿next指针线性访问,严格遵循写入序列。

3、删除元素时,仅断开其在双向链表中的前后指针,不移动其他 Bucket 位置,维持剩余元素顺序不变。

五、packed array 的整数索引优化路径

当数组满足全部为非负连续整数键(如 0,1,2,…,n−1)、无空洞、且元素数等于容量时,PHP 7+ 启用 packed array 模式。此时哈希表退化为纯索引数组,绕过哈希计算与链表跳转,极大提升数值索引访问性能。

1、判断条件由引擎在每次插入/删除后动态校验:

nNumOfElements == nTableSize && nNextFreeElement == nNumOfElements且所有键均为整数并连续。

2、访问

$arr[5]时,直接计算arData + 5 * sizeof(Bucket)地址偏移,无需哈希、无需链表遍历。

3、一旦插入字符串键或执行

unset($arr[2])造成空洞,packed array 状态立即失效,恢复为通用哈希表模式。

相关文章