← PHP字符串引用是什么?能解决哪些实际问题 没有下一篇了 →

PHP数组是什么?从底层结构到项目实战的完整讲解

著
原创 2026-05-22 PHP 已有人查阅

一、PHP数组不是“数组”,是有序哈希表

很多语言里,数组是一段连续内存,下标只能是整数。PHP不一样。PHP数组底层是哈希表(HashTable),同时维护了插入顺序。也就是说,它既能当索引数组用,也能当关联数组用,还能当有序映射用。

<?php
$arr = [
    'name' => '代码号',
    0      => '学习编程',
    'php'  => '教程',
];

print_r($arr);
// Array ( [name] => 代码号 [0] => 学习编程 [php] => 教程 )

键可以是整数,也可以是字符串。整数键和字符串键在底层会做归一化处理:"0"会被转成0,"1.5"保持字符串,true转成1,null转成空字符串""。这些规则不记住,调试时会很困惑。

个人经验:早期做一个表单接收功能,前端传id为字符串"0",后端用isset($data['id'])判断,结果因为键归一化,"0"和0是同一个键。当时排查了很久,后来养成习惯:接收外部数据后先明确键的类型,必要时用array_key_exists而不是isset,因为isset对null值返回false。

二、索引数组与关联数组:只是写法差异

<?php
// 索引数组
$langs = ['php', 'java', 'python'];

// 关联数组
$user = [
    'name' => '代码号',
    'lang' => 'php',
];

索引数组的键从0开始自动递增。如果手动指定了键,后续自动递增会从“较大整数键+1”继续。

<?php
$arr = [5 => 'a', 'b', 'c'];
print_r($arr);
// Array ( [5] => a [6] => b [7] => c )

这个细节在合并数组时容易出问题。array_merge会重新编号整数键,而+运算符保留原键,冲突时以左边为准。

<?php
$a = [1 => '代码号'];
$b = [1 => '学习编程', 2 => '教程'];

print_r($a + $b);          // [1] => 代码号 [2] => 教程
print_r(array_merge($a, $b)); // [0] => 代码号 [1] => 学习编程 [2] => 教程

用哪个取决于业务:配置合并通常用array_merge_recursive或array_replace_recursive,而列表拼接用array_merge更自然。用+合并列表,第二个数组的整数键会被吞掉,这是常见坑。

三、遍历:foreach是主力,但别忽略细节

<?php
$user = ['name' => '代码号', 'lang' => 'php'];

foreach ($user as $key => $value) {
    echo $key . ': ' . $value . PHP_EOL;
}

foreach操作的是数组的副本(写时复制),除非用引用。引用遍历的坑在上一条字符串引用里提过,这里再强调一次:

<?php
$nums = [1, 2, 3];

foreach ($nums as &$n) {
    $n *= 2;
}
unset($n); // 必须断开

print_r($nums); // [2, 4, 6]

如果忘了unset($n),后面再用$n赋值,数组之后一个元素会被改掉。这个坑在循环嵌套时更隐蔽,外层引用没断,内层循环又用了同名变量,数据就乱了。

另一种遍历是array_walk、array_map、array_filter这类函数式写法。它们不修改原数组(array_walk可以改),适合做数据清洗。

<?php
$langs = ['php', 'java', 'python'];
$upper = array_map('strtoupper', $langs);
print_r($upper); // [PHP, JAVA, PYTHON]

个人建议:简单遍历用foreach,批量转换用array_map,条件筛选用array_filter。不要为了“看起来高级”而强行函数式,可读性优先。

四、常用函数:按场景分类记

  • 增删改:array_push、array_pop、array_shift、array_unshift、array_splice

  • 查找:in_array、array_search、array_key_exists、isset

  • 排序:sort、rsort、asort、ksort、usort

  • 运算:array_diff、array_intersect、array_unique

  • 统计:count、array_sum、array_count_values

  • 提取:array_column、array_keys、array_values

其中array_column在项目里用得很多,比如从二维数组里取某一列:

<?php
$users = [
    ['id' => 1, 'name' => '代码号'],
    ['id' => 2, 'name' => '学习编程'],
];

$names = array_column($users, 'name');
print_r($names); // [代码号, 学习编程]

如果需要以id为键,可以传第三个参数:

<?php
$map = array_column($users, 'name', 'id');
print_r($map); // [1 => 代码号, 2 => 学习编程]

这个用法在“列表转映射”时省掉一个foreach,代码更短。

五、数组与引用:容易混淆的地方

数组变量赋值默认是值拷贝(写时复制)。但数组元素可以是引用:

<?php
$a = ['x' => '代码号'];
$b = &$a['x'];
$b = '学习编程';

print_r($a); // [x] => 学习编程

如果整个数组用引用赋值:

<?php
$a = ['x' => '代码号'];
$b = &$a;
$b['x'] = '学习编程';

print_r($a); // [x] => 学习编程

两种写法效果类似,但作用范围不同。前者只绑定一个元素,后者绑定整个数组。项目里做“配置共享”时,绑定整个数组更常见,但要注意:引用会跟着变量生命周期走,函数返回引用要格外小心。

六、性能取舍:大数组下的注意事项

PHP7以后数组性能提升明显,但仍有几个点值得注意:

  1. in_array在大数组里是线性查找。如果数组有几千个元素,且需要频繁判断存在性,改用array_flip后isset,或者直接用键做映射。

  2. array_merge在循环里调用会反复复制。合并多个数组时,先收集再一次性合并。

  3. array_unique对大型数组开销不小。如果只是去重后做键值映射,用array_flip两次也能达到类似效果。

  4. 内存:PHP数组每个元素占用空间比C数组大,百万级数据要考虑生成器或SplFixedArray。

<?php
// 不推荐:循环内反复合并
$result = [];
foreach ($chunks as $chunk) {
    $result = array_merge($result, $chunk);
}

// 推荐:先收集再一次合并
$result = array_merge(...$chunks);

项目反思:做过一个日志聚合功能,最初在循环里array_merge,数据量上万后明显变慢。改成先收集再合并,耗时下降了一半多。不是算法多高深,就是减少了重复复制。

七、项目实例:配置管理中的数组

<?php
$default = [
    'db' => ['host' => 'localhost', 'port' => 3306],
    'cache' => ['driver' => 'file'],
];

$env = [
    'db' => ['host' => '192.168.1.10'],
    'cache' => ['driver' => 'redis'],
];

$config = array_replace_recursive($default, $env);
print_r($config);
// db.host 被覆盖,db.port 保留,cache.driver 被覆盖

array_replace_recursive适合“默认配置+环境覆盖”。如果用array_merge_recursive,同键会合并成数组,反而不好用。这个区别在配置场景里很关键。

八、项目实例:数据分组

<?php
$orders = [
    ['user' => '代码号', 'amount' => 100],
    ['user' => '学习编程', 'amount' => 200],
    ['user' => '代码号', 'amount' => 50],
];

$grouped = [];
foreach ($orders as $order) {
    $grouped[$order['user']][] = $order['amount'];
}

print_r($grouped);
// [代码号 => [100, 50], 学习编程 => [200]]

这种“按键分组”的写法在统计、报表、消息聚合里非常常见。也可以用array_reduce实现,但foreach更直观。

PHP数组的灵活来自哈希表底座,代价是某些操作比纯数组慢。理解键归一化、写时复制、引用残留、合并函数差异这几个点,能避开大部分日常坑。项目里用数组,先想清楚是“列表”还是“映射”,再选对应的函数和遍历方式。把unset、array_column、array_replace_recursive这几个工具用熟,代码会干净很多。

← PHP字符串引用是什么?能解决哪些实际问题 没有下一篇了 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号