PHP的数组是关联数组(键值对形式),在底层是通过哈希表来实现的。每个数组元素都有一个键和一个值,PHP会根据键值计算哈希值,并存储相应的数据。其次,数组在内存中的实现是动态的,它可以随着元素的增减自动扩展或收缩。我们可以通过分析PHP源代码中的zend_hash结构,进一步了解哈希表的具体实现。PHP也会通过引用计数来优化内存管理,避免多余的内存消耗。
PHP数组是一个关联数组,即由键和值组成的键值对(key-value pairs)。键可以是整数或字符串,值可以是任意类型的数据。与传统的数组不同,PHP数组可以同时包含数字索引和字符串索引。
在PHP的源代码中,数组的底层实现并不是直接使用传统的数组数据结构,而是通过哈希表(hash table)来实现的。具体来说,PHP数组是由一个称为HashTable的结构表示的,它是通过哈希算法将键映射到相应的值的。
PHP数组的内存管理是动态的,也就是说,当你添加或删除数组元素时,PHP会根据需要调整数组的大小。这种动态扩展意味着数组在使用过程中不会固定大小,而是会随着元素的增减自动调整。
①计算键的哈希值
当插入一个元素时,PHP 会先根据键的类型(整数或字符串)计算出该键的哈希值:
● 对于整数键,哈希值就是这个整数的值。
● 对于字符串键,PHP 使用哈希函数(如 DJB2 或 MurmurHash)将字符串转换成一个整数哈希值。
②确定哈希表索引
根据哈希值通过算法,计算出这个元素应该存储在哈希表中的位置,即桶的索引
③是否有哈希冲突
如果索引位置没有数据,则直接插入
如果索引位置存在数据,则说明发生哈希冲突,则将该元素插入到当前位置,并使用链表指针将原有元素进行链接
④插入元素
插入时,PHP 会将这个键值对存储到对应的桶(Bucket)中:
● 桶中存储键、值、哈希值和链表指针。
● 如果是关联数组,键可以是字符串,值是元素数据。
● 如果是索引数组,键是整数。
插入过程示例
假设我们插入一个元素 arr['name'] = 'John',PHP 内部执行的过程如下:
①计算键的哈希值
当插入一个元素时,PHP 会先根据键的类型(整数或字符串)计算出该键的哈希值:
● 对于整数键,哈希值就是这个整数的值。
● 对于字符串键,PHP 使用哈希函数(如 DJB2 或 MurmurHash)将字符串转换成一个整数哈希值。
②确定哈希表索引
根据哈希值通过算法,计算出这个元素应该存储在哈希表中的位置,即桶的索引
③是否有哈希冲突
如果索引位置只有一个元素,且该元素的key与要查询的key相等,则直接返回
如果索引位置有多个元素,且第一个元素的key与要查询的key不相等,则说明发生哈希冲突,则根据链表指针向下寻找,知道找到与要查询的key享等的元素,然后返回元素值
④返回数据
找到对应数据则返回,没有找到匹配的键则返回错误或抛出异常。
当哈希表中的元素数量超过设定的阈值后,一般是容量的2/3,会出发动态扩容机制,以减少哈希冲突和提高插入和查询效率。
扩容过程
①:触发扩容
假如一个哈希表可以存放8个元素,现在已经存放6个,达到2/3的阈值,就会出发扩容。
②:扩展容量
PHP会将哈希表容量扩容为原来的两倍,当前容量是8个元素,则扩容到16个元素。
③:迁移旧数据
扩容后,将所有旧数据迁移到新的哈希表中,迁移过程中,会重新计算元素key的哈希值。
动态扩展的优点
● 减少冲突:通过扩展容量,哈希表中每个桶承载的元素数量减少,冲突也会相应减少,从而提高数组的性能。
● 提高效率:在扩容之后,插入和查找操作的时间复杂度可以保持在 O(1),避免哈希冲突造成的性能下降。
动态扩展的代价
● 额外的内存消耗:扩容时需要为新的哈希表分配更多内存,可能会临时占用双倍的内存(旧哈希表加新哈希表),直到扩容完成。
● 重哈希的计算开销:扩容过程中,PHP 需要重新计算所有元素的哈希值,并将它们重新插入到新的位置。这个过程会导致瞬时的性能开销。
内存管理
—— 评论区 ——