php数组底层是哈希表实现,空数组占约72字节,每个元素需32字节bucket及哈希表空间,故array_fill(0,10000,NULL)比填整数更耗内存,且扩容需重哈希。

PHP 数组在底层是哈希表(HashTable)实现的,内存占用远高于表面上看起来的“几个元素”,这是面试中常被深挖的点。理解其内存结构,才能解释为什么 array_fill(0, 10000, null) 比 array_fill(0, 10000, 1) 占更多内存,或为什么空数组也占约 72 字节。
PHP 数组底层是稠密哈希表,不是连续内存块
不同于 C 或 Go 的 slice,PHP 数组(zend_array)本质是带双向链表的哈希表:每个元素对应一个 bucket 结构,含 key、value、hash、next 等字段;整个数组还维护着 hash 表(指针数组)、冲突链、迭代器信息等元数据。
这意味着:
- 即使存一个整数,也要分配一个完整 bucket(通常 32 字节 x86_64),外加哈希表槽位(默认 8 个指针,64 字节)
- 数组扩容时不是简单 realloc,而是重建哈希表 + 重哈希所有元素,开销大
- 键类型影响结构:字符串 key 需额外分配内存存 key 内容并计算 hash;整数 key 虽省 key 存储,但不省 bucket 和 hash 计算逻辑
实际内存占用 = 基础结构 + 元素 bucket × 数量 + 键值内容存储
以 PHP 8.2 x86_64 为例:
立即学习“PHP免费学习笔记(深入)”;
- 空数组:约 72 字节(
zend_array头部 56 字节 + 默认哈希表 8×8=64 字节,但有共享优化,实测约 72B) - 每个整数元素:+32 字节 bucket + 可能的哈希表扩容(如从 8→16 槽,多占 64 字节)
- 每个字符串元素:
bucket32 字节 + 字符串 zval(16 字节)+ 字符串内容本身(如 “hello” 占 6 字节 + 1 字节结尾 + 对齐填充) -
array_fill(0, 10000, null)中的null是 zval 常量,不额外分配,但 10000 个 bucket 和至少 16384 槽哈希表(因负载因子限制)会占 ~600KB+
常见误区与优化提示
面试官常借此考察对底层和性能敏感度:
- “用数组模拟栈/队列?小心
array_push/array_shift在大数组上 O(n) 移动” → 改用 SplStack/SplQueue(底层双链表) - “大量小数组(如配置项)?考虑用对象或 json 字符串代替,避免哈希表固定开销”
- “遍历大数组用
foreach,别用for($i=0; $i<count> —— <code>count()虽是 O(1),但每次循环都调用仍引入函数调用开销,且易被误认为 O(n)” - “unset 后数组不会自动缩容,内存不释放;真要收缩可用
$arr = array_values($arr)强制重建(代价高,慎用)”
验证方法:memory_get_usage() + debug_zval_dump() 辅助分析
不能只看 count() 或 sizeof()(后者返回元素数,非字节数)。可靠方式:
-
memory_get_usage(true)获取真实内存分配(含未用但已申请的内存) -
debug_zval_dump($arr)查看引用计数和是否为 is_ref,辅助判断是否发生写时复制(COW)导致隐式内存增长 - 用
gc_collect_cycles()后再测,排除垃圾回收延迟干扰 - 注意 opcache 和 JIT 可能影响结果,建议关闭后测试