PHP 数组合并深度解析:掌握 array_merge、+ 操作符与更多高效技巧190

 

在PHP开发中,数组是我们处理和组织数据最核心的结构之一。从用户提交的表单数据,到数据库查询结果,再到应用程序的配置信息,数组无处不在。随着业务逻辑的复杂化,我们经常需要将一个或多个数组的数据整合到一起,也就是“合并数组”。然而,PHP提供了多种数组合并的方法,每种方法在处理键(key)和值(value)的冲突时都有其独特的行为。理解这些差异对于编写健壮、高效且无bug的PHP代码至关重要。

本文将作为一份全面的指南,深入探讨PHP中数组合并的各种方法,包括常用的内置函数、操作符以及PHP 7.4+引入的更现代的语法。我们将详细分析每种方法的特点、适用场景、注意事项以及性能考量,帮助你选择最合适的合并策略。

1. 最常用的合并函数:array_merge()

array_merge() 是PHP中最基础也最常用的数组合并函数。它的主要特点是:
数值键(Numeric Keys):如果数组包含数值键,array_merge() 会将它们重新索引,从0开始递增。这意味着即使原始数组有相同的数值键,它们也不会被覆盖,而是作为新元素追加到结果数组的末尾。
字符串键(String Keys):如果数组包含字符串键,且多个输入数组中存在相同的字符串键,则后一个数组的值会覆盖前一个数组的值。

语法:


array array_merge ( array ...$arrays )

示例:


处理数值键


<?php
$array1 = ['apple', 'banana']; // 键为 0, 1
$array2 = ['cherry', 'date']; // 键为 0, 1
$result = array_merge($array1, $array2);
print_r($result);
// 输出:
// Array
// (
// [0] => apple
// [1] => banana
// [2] => cherry
// [3] => date
// )
?>

可以看到,尽管两个数组都有 `0` 和 `1` 两个数值键,但它们都被重新索引并追加了。

处理字符串键


<?php
$user_settings_default = [
'theme' => 'dark',
'language' => 'en',
'notifications' => true
];
$user_settings_custom = [
'language' => 'zh-CN',
'notifications' => false,
'timezone' => 'Asia/Shanghai'
];
$final_settings = array_merge($user_settings_default, $user_settings_custom);
print_r($final_settings);
// 输出:
// Array
// (
// [theme] => dark
// [language] => zh-CN
// [notifications] =>
// [timezone] => Asia/Shanghai
// )
?>

在这个例子中,`language` 和 `notifications` 的值被 `$user_settings_custom` 中的值覆盖,而 `theme` 和 `timezone` 则被保留或添加。

注意事项:



如果传入非数组参数,array_merge() 会返回 null 并发出一个 E_WARNING 级别的警告。
如果只传入一个数组,或者所有数组都是空的,array_merge() 将返回该数组本身或一个空数组。

2. 数组联合操作符:+

与 array_merge() 不同,+ 操作符(称为数组联合操作符)在合并数组时对键的处理方式截然不同。它的核心原则是:
键保留原则:+ 操作符会保留第一个数组中的所有键。如果后一个数组中存在与第一个数组相同的键,则后一个数组中该键的值会被忽略,不会覆盖第一个数组的值。
数值键:数值键的行为与字符串键一致,即第一个数组中的数值键优先。它们不会被重新索引

语法:


$array_result = $array1 + $array2;

示例:


处理数值键


<?php
$array1 = ['apple', 'banana']; // 键为 0, 1
$array2 = ['cherry', 'date']; // 键为 0, 1
$result = $array1 + $array2;
print_r($result);
// 输出:
// Array
// (
// [0] => apple
// [1] => banana
// )
?>

如你所见,由于 `$array1` 中已经存在键 `0` 和 `1`,所以 `$array2` 中相同键的值被完全忽略了。这与 array_merge() 的行为形成鲜明对比。

处理字符串键


<?php
$user_settings_default = [
'theme' => 'dark',
'language' => 'en',
'notifications' => true
];
$user_settings_custom = [
'language' => 'zh-CN',
'notifications' => false,
'timezone' => 'Asia/Shanghai'
];
$final_settings = $user_settings_default + $user_settings_custom;
print_r($final_settings);
// 输出:
// Array
// (
// [theme] => dark
// [language] => en
// [notifications] => 1
// [timezone] => Asia/Shanghai
// )
?>

这里,`language` 和 `notifications` 的值仍保留了 `$user_settings_default` 中的值,因为它们的键在 `$user_settings_default` 中已经存在。`timezone` 则被添加进来。

注意事项:



+ 操作符的优先级低于算术运算符。
如果操作数中包含非数组类型,PHP 7.0+ 会抛出 TypeError;在PHP 5.x 中,会发出 E_WARNING 警告并将非数组值视为一个空数组。
这个操作符非常适合用默认值填充数组,因为它确保了第一个数组(通常是默认值)的键值对不会被覆盖,除非在第一个数组中不存在该键。

3. 递归合并数组:array_merge_recursive()

当你的数组中包含嵌套数组(多维数组),并且你希望以递归的方式合并它们时,array_merge_recursive() 就派上用场了。
数值键:与 array_merge() 类似,数值键会被重新索引并追加。
字符串键:如果相同的字符串键对应的值都是非数组类型,则这些值会被放入一个新的数组中。如果相同字符串键对应的值是数组,则会递归地合并这些子数组。

语法:


array array_merge_recursive ( array ...$arrays )

示例:


<?php
$config_default = [
'database' => [
'host' => 'localhost',
'port' => 3306,
'username' => 'root'
],
'settings' => [
'debug' => false,
'logging' => ['level' => 'info']
],
'environment' => 'development'
];
$config_production = [
'database' => [
'host' => 'prod_db_server',
'password' => 'secure_pass'
],
'settings' => [
'debug' => false,
'logging' => ['file' => '/var/log/']
],
'environment' => 'production',
'cache' => true
];
$final_config = array_merge_recursive($config_default, $config_production);
print_r($final_config);
// 输出:
// Array
// (
// [database] => Array
// (
// [host] => prod_db_server
// [port] => 3306
// [username] => root
// [password] => secure_pass
// )
// [settings] => Array
// (
// [debug] =>
// [logging] => Array
// (
// [level] => info
// [file] => /var/log/
// )
// )
// [environment] => Array
// (
// [0] => development
// [1] => production
// )
// [cache] => 1
// )
?>

在这个例子中:
`database` 子数组被递归合并,`host` 被覆盖,`password` 被添加。
`settings` 子数组也被递归合并,`debug` 被覆盖,`logging` 子数组继续递归合并,`level` 和 `file` 都被保留。
`environment` 键的值是字符串,在两个数组中都存在,所以 array_merge_recursive() 将它们合并成一个包含两个元素的数组 `['development', 'production']`。

注意事项:



当非数组的同名字符串键冲突时,array_merge_recursive() 会将它们的值放入一个新的数组中。这可能不是你期望的行为,特别是在配置合并时。如果你希望覆盖,而不是创建新数组,你可能需要自定义递归合并函数或使用其他策略(如 array_replace_recursive())。

4. PHP 7.4+ 的现代合并语法:Spread Operator (...)

PHP 7.4 引入了数组的 Spread Operator (...),为数组合并提供了一种更简洁、更现代的语法。它的行为与 array_merge() 非常相似。
数值键:会被重新索引。
字符串键:后一个数组的值会覆盖前一个数组的值。

语法:


$new_array = [...$array1, ...$array2];

示例:


<?php
$array1 = ['a' => 1, 'b' => 2, 0 => 'x'];
$array2 = ['b' => 3, 'c' => 4, 0 => 'y'];
$mergedArray = [...$array1, ...$array2];
print_r($mergedArray);
// 输出:
// Array
// (
// [a] => 1
// [b] => 3
// [0] => x
// [1] => y
// [c] => 4
// )
?>

在这个例子中,字符串键 `b` 的值被覆盖,而数值键 `0` 则被重新索引,`0` 对应 `x`,`1` 对应 `y`。

注意事项:



此功能仅适用于 PHP 7.4 及更高版本。
它提供了与 array_merge() 几乎相同的功能,但在某些情况下,尤其是在声明数组时,可以使代码更具可读性。

5. 自定义合并逻辑:使用循环(foreach)

在某些复杂场景下,内置函数可能无法满足你特定的合并需求,例如:
你希望在合并时执行自定义的冲突解决逻辑。
你只想合并特定条件下的元素。
你需要对嵌套数组进行深度合并,但 array_merge_recursive() 的行为不符合预期。

这时,你可以使用循环(通常是 foreach)来手动遍历数组并构建你的结果数组。

示例:合并并移除重复项(基于值)


<?php
$list1 = ['apple', 'banana', 'orange'];
$list2 = ['banana', 'grape', 'kiwi'];
$merged_unique = $list1; // 从第一个列表开始
foreach ($list2 as $item) {
if (!in_array($item, $merged_unique)) {
$merged_unique[] = $item; // 如果不存在,则添加
}
}
print_r($merged_unique);
// 输出:
// Array
// (
// [0] => apple
// [1] => banana
// [2] => orange
// [3] => grape
// [4] => kiwi
// )
?>

示例:深度递归合并(覆盖非数组值)


如前所述,array_merge_recursive() 会将冲突的非数组值合并成一个数组。如果你想要在递归合并时直接覆盖它们,可以编写一个自定义函数:<?php
function array_merge_deep_overwrite(array &$array1, array &$array2): array
{
$merged = $array1;
foreach ($array2 as $key => $value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = array_merge_deep_overwrite($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
$config_default = [
'database' => [
'host' => 'localhost',
'username' => 'root'
],
'settings' => [
'debug' => false,
'logging' => ['level' => 'info']
],
'environment' => 'development'
];
$config_production = [
'database' => [
'host' => 'prod_db_server',
'password' => 'secure_pass'
],
'settings' => [
'debug' => true, // 注意这里从 false 变为 true
'logging' => ['file' => '/var/log/']
],
'environment' => 'production' // 注意这里从 development 变为 production
];
$final_config = array_merge_deep_overwrite($config_default, $config_production);
print_r($final_config);
// 输出:
// Array
// (
// [database] => Array
// (
// [host] => prod_db_server
// [username] => root
// [password] => secure_pass
// )
// [settings] => Array
// (
// [debug] => 1
// [logging] => Array
// (
// [level] => info
// [file] => /var/log/
// )
// )
// [environment] => production
// )
?>

这个自定义函数完美地实现了我们对配置文件的深度覆盖合并。

6. 何时选择哪种方法?

选择正确的数组合并方法是提高代码效率和可维护性的关键。以下是一些指导原则:

简单追加,并重新索引数值键 (如日志、数据列表):

使用 array_merge()。
在 PHP 7.4+ 中,使用 Spread Operator (...) 是一个更简洁的选择。



用默认值填充数组,确保第一个数组的键优先 (如用户配置、插件设置):

使用 + 操作符。它非常适合将默认配置与用户自定义配置合并,同时确保用户未设置的项使用默认值,用户已设置的项不会被默认值覆盖。



深度合并多维数组,且可以接受相同字符串键的值被合并成一个新数组 (如某些复杂的日志聚合):

使用 array_merge_recursive()。



深度合并多维数组,但希望相同字符串键的值直接覆盖 (如配置文件、复杂的用户设置):

使用 array_replace_recursive()(如果仅需简单覆盖)。
或者编写自定义的递归合并函数(如上面 array_merge_deep_overwrite 示例),以实现更精细的控制。



具有特定条件或复杂逻辑的合并 (如根据某个ID合并对象,去重,特定规则的冲突解决):

使用 foreach 循环或其他迭代方法进行自定义合并。



7. 性能考量与最佳实践

对于绝大多数应用场景,PHP内置的数组合并函数都是经过高度优化的,性能通常不是瓶颈。在选择合并方法时,首先应考虑的是代码的清晰度、正确性以及是否符合业务逻辑,而不是过早地进行微优化。
理解键的处理方式: 这是最重要的。不同的方法对数值键和字符串键的处理差异巨大,务必根据你的数据结构和预期结果来选择。
输入验证: 在生产代码中,始终要确保传入合并函数的参数是数组。否则,可能会导致警告、错误甚至意外的行为。
可读性: 优先选择最能清晰表达你意图的方法。如果一个简单的 array_merge() 能解决问题,就不要使用复杂的循环。
array_replace() 和 array_replace_recursive(): 这两个函数与 array_merge() 的行为类似,但它们在处理数值键时不会重新索引,而是尝试覆盖。如果数值键也需要覆盖而不是追加,它们可能是更好的选择。不过,由于篇幅限制,本文未详细展开,但值得你在特定场景下进行研究。


PHP提供了丰富而强大的数组合并工具,从简单的 array_merge() 到精细控制的自定义循环,每种方法都有其独特的用途和行为。作为一名专业的PHP开发者,深入理解这些差异,并根据具体的业务需求和数据特性选择最合适的合并策略,是编写高质量、高效能代码的关键。

希望本文能帮助你全面掌握PHP数组合并的艺术,让你在日常开发中游刃有余。

2025-11-23


上一篇:PHP 精准获取本周周日:多种方法与最佳实践详解

下一篇:PHP cURL 高效检测与处理 HTTP 404 错误:从原理到实践