PHP 数组合并终极指南:从基础到高级,掌握多种核心方法与技巧101

```html

在 PHP 开发中,数组是一种极其灵活且强大的数据结构,用于存储和操作数据集合。作为一名专业的程序员,你几乎每天都会遇到需要将两个或多个数组合并成一个新数组的场景。无论是整合用户输入、合并配置文件、处理数据库查询结果,还是组装API响应,有效地合并数组都是一项核心技能。本文将深入探讨 PHP 中合并数组的各种方法,从基础函数到高级技巧,帮助你理解它们之间的差异、适用场景以及潜在的“坑”,最终让你能够根据具体需求选择最合适的合并策略。

一、为什么需要合并数组?常见场景概述

在深入探讨具体方法之前,我们先来回顾一下为什么数组合并如此重要,以及它在实际开发中可能出现的场景:
配置合并:将默认配置与用户自定义配置合并,用户配置覆盖默认配置。
数据整合:从不同数据源(例如数据库查询、API请求)获取的数据,需要合并到一个统一的数组结构中。
表单处理:将用户通过多个表单域提交的数据合并成一个完整的记录。
购物车或订单:合并不同商品项、数量、价格等信息。
国际化(i18n):合并不同语言的翻译字符串数组。
运行时数据组装:动态构建或修改数据结构以满足程序逻辑。

理解这些场景有助于我们在选择合并方法时做出更明智的决策。

二、核心合并方法详解

PHP 提供了多种内置函数和操作符来合并数组,它们在处理键(Key)和值(Value)时各有不同。我们将逐一介绍这些方法。

1. array_merge():最常用且直观的方法


array_merge() 是 PHP 中最常见也是最基础的数组合并函数。它接受任意数量的数组作为参数,并按照它们出现的顺序将元素合并到一个新数组中。其行为特点如下:
数值键(Numeric Keys):如果数组包含数值键,array_merge() 会将它们重新索引,从 0 开始递增。这意味着原有的数值键会被忽略,新数组会有一个新的顺序。
字符串键(String Keys):如果数组包含字符串键,并且多个数组中存在相同的字符串键,后一个数组的值会覆盖前一个数组的值。

示例:


<?php
$array1 = ['a', 'b', 'c'];
$array2 = ['d', 'e', 'f'];
$result1 = array_merge($array1, $array2);
print_r($result1);
// Output: Array ( [0] => a [1] => b [2] => c [3] => d [4] => e [5] => f )
$array3 = ['name' => 'Alice', 'age' => 30];
$array4 = ['age' => 31, 'city' => 'New York'];
$result2 = array_merge($array3, $array4);
print_r($result2);
// Output: Array ( [name] => Alice [age] => 31 [city] => New York )
$array5 = [0 => 'red', 1 => 'green'];
$array6 = [0 => 'blue', 2 => 'yellow'];
$result3 = array_merge($array5, $array6);
print_r($result3);
// Output: Array ( [0] => red [1] => green [2] => blue [3] => yellow )
// 注意:虽然array6中0键值是'blue',但因为是数值键,会重新索引,所以'blue'是作为新元素添加的。
$array7 = ['name' => 'Bob', 'colors' => ['red', 'green']];
$array8 = ['age' => 25, 'colors' => ['blue']];
$result4 = array_merge($array7, $array8);
print_r($result4);
// Output: Array ( [name] => Bob [colors] => Array ( [0] => blue ) [age] => 25 )
// 注意:对于嵌套数组,array_merge() 不会进行递归合并,而是直接覆盖整个嵌套数组。
?>

适用场景:


当你需要将多个列表或字典简单地拼接起来,并且接受数值键重新索引以及字符串键覆盖行为时,array_merge() 是一个简单高效的选择。

2. 数组联合操作符 (+):保留左侧数组的键


PHP 的数组联合操作符 +(Union Operator)提供了一种不同的合并行为。它将右侧数组的元素添加到左侧数组中,但有一个关键的区别:
键冲突处理:如果左右两侧数组的键相同,无论该键是数值键还是字符串键,左侧数组的值将始终被保留,右侧数组中重复键的值会被忽略。
数值键:不会重新索引。

示例:


<?php
$array1 = ['a', 'b', 'c']; // 等价于 [0 => 'a', 1 => 'b', 2 => 'c']
$array2 = ['d', 'e', 'f']; // 等价于 [0 => 'd', 1 => 'e', 2 => 'f']
$result1 = $array1 + $array2;
print_r($result1);
// Output: Array ( [0] => a [1] => b [2] => c [3] => f )
// 解释:$array1的键0,1,2被保留,然后$array2中不冲突的键(这里是3)被添加。但这里array2的d,e的键是0,1,与array1冲突,所以被忽略。
// 注意:自动生成的数值键0,1,2与array1的键0,1,2冲突,因此只有array1的元素被保留,array2中实际没有新的非冲突键。
// 这是一个常见的误解,初学者可能会认为它会像array_merge一样追加。
// 更清晰的数值键示例
$array_a = [0 => 'apple', 1 => 'banana'];
$array_b = [0 => 'orange', 2 => 'grape'];
$result_ab = $array_a + $array_b;
print_r($result_ab);
// Output: Array ( [0] => apple [1] => banana [2] => grape )
// 解释:$array_a的0,1键被保留,由于$array_b的0键与$array_a冲突,'orange'被忽略。$array_b的2键不冲突,所以'grape'被添加。
$array3 = ['name' => 'Alice', 'age' => 30];
$array4 = ['age' => 31, 'city' => 'New York'];
$result2 = $array3 + $array4;
print_r($result2);
// Output: Array ( [name] => Alice [age] => 30 [city] => New York )
// 解释:键'name'和'age'在$array3中已存在,因此$array4中的'age' => 31被忽略。
?>

适用场景:


当你有默认值数组,希望用另一个数组来“填充”或“扩展”它,并且优先保留默认数组(左侧)的值时,+ 操作符非常有用。例如,设置函数或方法的默认参数。

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


array_merge() 在处理嵌套数组时会直接覆盖整个子数组,这在某些情况下可能不是我们想要的行为。array_merge_recursive() 函数正是为了解决这个问题而设计的,它会递归地合并数组。
数值键:与 array_merge() 类似,会重新索引并追加所有数值键的值。
字符串键:如果遇到相同的字符串键,并且它们的值都是数组,array_merge_recursive() 会递归地合并这两个子数组。如果其中一个值不是数组(例如字符串或数字),或者两者都是非数组,则会形成一个包含这两个值的数组。

示例:


<?php
$array1 = ['item' => 'Book', 'details' => ['author' => 'John', 'year' => 2020]];
$array2 = ['price' => 25, 'details' => ['publisher' => 'ABC', 'year' => 2021]];
$result = array_merge_recursive($array1, $array2);
print_r($result);
/*
Output:
Array
(
[item] => Book
[details] => Array
(
[author] => John
[year] => Array
(
[0] => 2020
[1] => 2021
)
[publisher] => ABC
)
[price] => 25
)
*/
$array3 = ['colors' => ['red', 'green']];
$array4 = ['colors' => ['blue', 'yellow']];
$result2 = array_merge_recursive($array3, $array4);
print_r($result2);
/*
Output:
Array
(
[colors] => Array
(
[0] => red
[1] => green
[2] => blue
[3] => yellow
)
)
*/
?>

注意事项:


array_merge_recursive() 在处理相同字符串键的非数组值时,会将它们合并成一个新的数值键数组。这可能不是你期望的行为(例如,你可能希望覆盖而不是合并成新数组)。

适用场景:


当你需要深度合并复杂的嵌套数据结构时,例如合并多个配置文件或语言包,它非常有用。

4. array_replace() 和 array_replace_recursive():替换和覆盖


array_replace() 和 array_replace_recursive() 函数与 array_merge() 和 array_merge_recursive() 类似,但它们在处理键和值时有一些关键区别,尤其是在数值键方面。

array_replace():



数值键:不会重新索引。如果目标数组和替换数组都有相同的数值键,替换数组的值会覆盖目标数组的值。如果只有替换数组有某个数值键,则会添加。
字符串键:与 array_merge() 类似,替换数组的值会覆盖目标数组的值。

示例:


<?php
$base = ['apple', 'banana', 'cherry']; // 0=>apple, 1=>banana, 2=>cherry
$replacements = [1 => 'orange', 3 => 'grape']; // 1=>orange, 3=>grape
$result = array_replace($base, $replacements);
print_r($result);
// Output: Array ( [0] => apple [1] => orange [2] => cherry [3] => grape )
// 解释:键1的值被'orange'替换,键3被添加。
$config_default = ['host' => 'localhost', 'port' => 80, 'env' => 'dev'];
$config_user = ['port' => 443, 'env' => 'prod', 'timeout' => 30];
$final_config = array_replace($config_default, $config_user);
print_r($final_config);
// Output: Array ( [host] => localhost [port] => 443 [env] => prod [timeout] => 30 )
?>

array_replace_recursive():


与 array_merge_recursive() 类似,但它会递归地替换子数组,而不是合并它们。它也遵循 array_replace() 对于数值键不重新索引的规则。

示例:


<?php
$config1 = [
'database' => [
'host' => 'localhost',
'port' => 3306,
'user' => 'root',
],
'app' => [
'name' => 'MyApp',
'debug' => true,
],
];
$config2 = [
'database' => [
'port' => 5432, // Override port
'password' => 'secret', // Add password
],
'app' => [
'debug' => false, // Override debug
'log_level' => 'info', // Add log_level
],
];
$merged_config = array_replace_recursive($config1, $config2);
print_r($merged_config);
/*
Output:
Array
(
[database] => Array
(
[host] => localhost
[port] => 5432
[user] => root
[password] => secret
)
[app] => Array
(
[name] => MyApp
[debug] =>
[log_level] => info
)
)
*/
?>

适用场景:


array_replace() 和 array_replace_recursive() 在合并配置文件、默认选项或任何需要严格覆盖行为的场景中非常有用,特别是当你希望保留数值键而不重新索引时。

5. 循环遍历(foreach):自定义合并逻辑


如果以上内置函数无法满足你特定的合并逻辑(例如,需要对重复的值进行某种计算,而不是简单覆盖或追加),那么使用 foreach 循环遍历数组是实现自定义合并的最佳方式。

示例:合并购物车商品数量


<?php
$cart1 = [
'item_id_1' => ['name' => 'Book', 'qty' => 2, 'price' => 10],
'item_id_2' => ['name' => 'Pen', 'qty' => 5, 'price' => 2],
];
$cart2 = [
'item_id_1' => ['name' => 'Book', 'qty' => 1, 'price' => 10], // 相同商品
'item_id_3' => ['name' => 'Notebook', 'qty' => 3, 'price' => 5],
];
$merged_cart = $cart1; // 从第一个购物车开始
foreach ($cart2 as $item_id => $item_details) {
if (isset($merged_cart[$item_id])) {
// 如果商品已存在,则合并数量
$merged_cart[$item_id]['qty'] += $item_details['qty'];
} else {
// 否则,直接添加新商品
$merged_cart[$item_id] = $item_details;
}
}
print_r($merged_cart);
/*
Output:
Array
(
[item_id_1] => Array
(
[name] => Book
[qty] => 3
[price] => 10
)
[item_id_2] => Array
(
[name] => Pen
[qty] => 5
[price] => 2
)
[item_id_3] => Array
(
[name] => Notebook
[qty] => 3
[price] => 5
)
)
*/
?>

适用场景:


当你需要精确控制每个元素的合并行为,或者需要执行复杂的业务逻辑(如求和、求平均值、去重等)时,循环遍历提供了最大的灵活性。

6. 扩展运算符 (...) (PHP 7.4+):更简洁的合并语法


PHP 7.4 引入了数组解包(Array Spreading)特性,允许你在数组定义或函数调用中直接使用 ... 操作符来解包数组。这为合并数组提供了一种非常简洁的语法,其行为类似于 array_merge()。
数值键:会重新索引,从 0 开始递增。
字符串键:如果存在相同的字符串键,后一个数组的值会覆盖前一个数组的值。

示例:


<?php
$array1 = [1, 2, 3];
$array2 = [4, 5, 6];
$result1 = [...$array1, ...$array2];
print_r($result1);
// Output: Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 )
$array3 = ['name' => 'Alice', 'age' => 30];
$array4 = ['age' => 31, 'city' => 'New York'];
$result2 = [...$array3, ...$array4];
print_r($result2);
// Output: Array ( [name] => Alice [age] => 31 [city] => New York )
// 也可以与其他元素混合使用
$array5 = [0, ...$array1, 99];
print_r($array5);
// Output: Array ( [0] => 0 [1] => 1 [2] => 2 [3] => 3 [4] => 99 )
?>

适用场景:


当你使用 PHP 7.4 或更高版本,并且希望以简洁、现代的方式执行类似于 array_merge() 的操作时,扩展运算符是首选。

三、高级合并技巧与注意事项

1. array_combine():用一个数组作键,另一个作值


严格来说,array_combine() 并不是“合并”两个已有数组,而是利用两个单独的数组来“构造”一个新的关联数组,一个提供键,一个提供值。
要求两个输入数组的元素数量必须完全相同。
第一个数组的元素将作为新数组的键,第二个数组的元素将作为新数组的值。

示例:


<?php
$keys = ['id', 'name', 'email'];
$values = [101, 'John Doe', 'john@'];
$user_data = array_combine($keys, $values);
print_r($user_data);
// Output: Array ( [id] => 101 [name] => John Doe [email] => john@ )
// 如果数量不匹配,会返回false并产生警告
$keys_short = ['id', 'name'];
$values_long = [101, 'John Doe', 'john@'];
$error_result = array_combine($keys_short, $values_long); // 警告:array_combine(): Both parameters should have an equal number of elements
var_dump($error_result); // Output: bool(false)
?>

适用场景:


当你拥有两个独立的、顺序匹配的列表,一个包含键名,另一个包含对应的值时,array_combine() 是非常高效和清晰的选择。

2. 性能考量


对于大多数 Web 应用场景,处理的数组规模通常不会导致明显的性能瓶颈。PHP 内置的数组操作函数是经过高度优化的 C 代码实现,通常比手写的 PHP 循环更高效。
小到中等数组:array_merge(), + 操作符, 扩展运算符, array_replace() 等内置函数是首选,因为它们简洁且性能良好。
大型数组:如果处理包含数十万甚至数百万元素的超大型数组,性能可能会成为问题。此时,应仔细考虑算法复杂度,并可能需要采用更底层的优化策略,例如分块处理或使用 SPL 数据结构,但这种情况在日常 PHP 开发中并不常见。对于绝大多数情况,内置函数足够。

3. 不可变性与可变性


PHP 数组在进行赋值或作为函数参数传递时,通常会进行“写时复制”(copy-on-write)。这意味着当你将一个数组赋值给另一个变量时,或者将其传入一个函数时,PHP 并不会立即创建一个完整的副本,而是共享底层的存储。只有当其中一个数组被修改时,才会创建副本。因此,所有我们讨论的合并函数都会返回一个全新的数组,而不会修改原始数组,这符合函数式编程中“不可变性”的良好实践。<?php
$original_array = ['a', 'b'];
$merged_array = array_merge($original_array, ['c']);
print_r($original_array); // Output: Array ( [0] => a [1] => b ) - 原始数组未被修改
print_r($merged_array); // Output: Array ( [0] => a [1] => b [2] => c ) - 返回新数组
?>

四、选择合适的合并策略

在多种合并方法中选择最合适的一个,关键在于理解它们在键冲突、数值键处理以及嵌套数组行为上的差异。
简单追加,数值键重新索引,字符串键覆盖:使用 array_merge() 或 PHP 7.4+ 的扩展运算符 [...$arr1, ...$arr2]。
保留左侧数组的键,添加不冲突的元素:使用 + 联合操作符。适用于设置默认值或优先级场景。
深度递归合并嵌套数组,数值键追加,字符串键值合并为新数组:使用 array_merge_recursive()。注意其处理非数组值合并的行为。
替换现有键值(包括数值键),不重新索引:使用 array_replace()。
深度递归替换嵌套数组和键值:使用 array_replace_recursive()。适用于复杂的配置覆盖。
从两个独立列表创建关联数组:使用 array_combine()。
需要高度定制的合并逻辑(如求和、特定条件筛选):使用 foreach 循环或其他更高级的数组函数(如 array_map, array_reduce)。

理解这些工具的细微差别,将使你能够编写出更健壮、更高效、更易于维护的 PHP 代码。

五、总结

PHP 提供了丰富而灵活的数组合并机制,从简单的拼接、覆盖,到复杂的递归合并和自定义逻辑,几乎可以满足所有场景的需求。作为一名专业的程序员,熟练掌握 array_merge()、+ 操作符、array_merge_recursive()、array_replace()、array_combine() 以及 PHP 7.4+ 的扩展运算符,并能根据实际业务场景选择最合适的工具,是提升开发效率和代码质量的关键。

在选择时,始终优先考虑 PHP 内置的数组函数,因为它们经过高度优化。只有当内置函数无法满足特定、复杂的合并逻辑时,才考虑通过循环或自定义函数来实现。通过本文的详细介绍和示例,相信你现在已经对 PHP 数组合并有了全面而深入的理解,并能在未来的开发中游刃有余地处理各种数组合并任务。```

2026-03-02


下一篇:PHP代码执行效率深度解析:从解释器到JIT编译与高级优化手段