PHP数组合并:深度解析与高性能实践指南175


在PHP编程中,数组作为最常用的数据结构之一,承载着大量的数据存储和处理任务。无论是从数据库查询结果、API接口响应,还是用户输入、配置文件读取,数据往往以数组的形式存在。因此,高效、准确地操作和合并数组,是每个PHP开发者必备的核心技能。特别是当我们需要将来自不同源或经过不同处理阶段的数组拼接起来时,理解PHP提供的各种数组拼接(合并)函数及其行为差异至关重要。

本文将作为一份详尽的指南,深入探讨PHP中用于数组合并的各种方法,包括内置函数、运算符以及现代PHP引入的新特性。我们将详细解析它们在处理索引数组、关联数组和多维数组时的不同表现,提供丰富的代码示例,并在此基础上讨论性能考量和最佳实践,帮助你根据具体场景选择最合适的合并策略。

一、`array_merge()`:最常用且强大的合并函数

`array_merge()` 是PHP中最常用也最直观的数组合并函数。它的主要功能是将一个或多个数组合并成一个新数组。

1.1 语法




array_merge(array ...$arrays): array

1.2 工作原理


`array_merge()` 的工作原理可以概括为以下几点:
索引数组(Numeric Keys):如果数组包含数字键名,`array_merge()` 会对它们进行重新索引,从0开始递增。所有来自源数组的元素都会被追加到新数组中。
关联数组(String Keys):如果数组包含字符串键名,`array_merge()` 会将相同键名的值进行覆盖。后传入的数组中的同名键值会覆盖先传入数组中的键值。
多数组合并:你可以向 `array_merge()` 传递任意数量的数组。它们会按照传入的顺序依次合并,后续数组的同名关联键会覆盖之前的。

1.3 代码示例


示例1:索引数组合并



$array1 = ['apple', 'banana'];
$array2 = ['orange', 'grape'];
$mergedArray = array_merge($array1, $array2);
print_r($mergedArray);
// 输出: Array ( [0] => apple [1] => banana [2] => orange [3] => grape )

示例2:关联数组合并



$user1 = ['name' => 'Alice', 'age' => 30];
$user2 = ['city' => 'New York', 'age' => 31]; // 'age'键名重复
$mergedUser = array_merge($user1, $user2);
print_r($mergedUser);
// 输出: Array ( [name] => Alice [age] => 31 [city] => New York )
// 注意:user2中的'age'覆盖了user1中的'age'

示例3:混合数组合并



$configDefault = ['host' => 'localhost', 'port' => 80, 'log_level' => 'info'];
$configUser = [0 => 'custom_setting', 'port' => 443];
$finalConfig = array_merge($configDefault, $configUser);
print_r($finalConfig);
// 输出: Array ( [host] => localhost [port] => 443 [log_level] => info [0] => custom_setting )
// 数字键'0'被追加,'port'被覆盖

示例4:传递一个空数组或非数组

如果传递给 `array_merge()` 的参数不是数组或是一个空数组,它会被忽略。如果所有参数都不是数组,或者只传入了一个非数组参数,它会抛出 `TypeError`。



$arrayA = ['a', 'b'];
$arrayB = []; // 空数组
$arrayC = array_merge($arrayA, $arrayB);
print_r($arrayC);
// 输出: Array ( [0] => a [1] => b )
// $invalidParam = "string";
// $mergedInvalid = array_merge($arrayA, $invalidParam); // 这会抛出 TypeError

二、`+` 运算符:独特的合并策略

PHP的数组 `+` 运算符(联合运算符)也用于数组合并,但其行为与 `array_merge()` 存在显著差异,尤其是在处理具有相同键名的元素时。

2.1 语法




$array1 + $array2

2.2 工作原理


`+` 运算符的合并策略是:
保留左侧数组的键值:如果两个数组中存在相同的键名(无论是数字键还是字符串键),`+` 运算符会保留左侧(第一个)数组中的值,而忽略右侧(第二个)数组中对应键的值。
追加右侧数组的唯一键值:右侧数组中所有在左侧数组中不存在的键值对会被追加到合并后的数组中。
不重新索引:无论键名是数字还是字符串,`+` 运算符都不会重新索引数字键。原始的键名会得到保留。

2.3 代码示例


示例1:关联数组合并



$user1 = ['name' => 'Alice', 'age' => 30];
$user2 = ['city' => 'New York', 'age' => 31]; // 'age'键名重复
$mergedUser = $user1 + $user2;
print_r($mergedUser);
// 输出: Array ( [name] => Alice [age] => 30 [city] => New York )
// 注意:user1中的'age' (30) 被保留,user2中的'age' (31) 被忽略

示例2:索引数组合并



$array1 = [0 => 'apple', 1 => 'banana'];
$array2 = [1 => 'orange', 2 => 'grape']; // 键'1'重复,键'2'唯一
$mergedArray = $array1 + $array2;
print_r($mergedArray);
// 输出: Array ( [0] => apple [1] => banana [2] => grape )
// 注意:array1中的'banana'被保留,array2中的'orange'被忽略,array2中的'grape'被追加

示例3:混合键名



$configA = ['name' => 'App', 0 => 'setting1'];
$configB = ['version' => '1.0', 0 => 'setting2'];
$finalConfig = $configA + $configB;
print_r($finalConfig);
// 输出: Array ( [name] => App [0] => setting1 [version] => 1.0 )
// 键'0','setting1'被保留,'setting2'被忽略

2.4 何时使用 `+` 运算符?


当你需要合并两个数组,并且希望“左侧数组”中的值优先,只有当“左侧数组”中没有某个键时才从“右侧数组”中添加,那么 `+` 运算符是理想选择。这在设置默认值或用户配置时非常有用,例如:



$defaultSettings = [
'theme' => 'light',
'items_per_page' => 10,
'debug_mode' => false
];
$userSettings = [
'items_per_page' => 20,
'language' => 'en'
];
$finalSettings = $userSettings + $defaultSettings; // 用户设置优先
print_r($finalSettings);
// 输出: Array ( [items_per_page] => 20 [language] => en [theme] => light [debug_mode] => false )

三、`array_merge_recursive()`:深度递归合并

当处理多维关联数组,特别是希望相同键名的子数组能够被“深度”合并而不是简单覆盖时,`array_merge_recursive()` 函数就派上用场了。

3.1 语法




array_merge_recursive(array ...$arrays): array

3.2 工作原理


与 `array_merge()` 类似,`array_merge_recursive()` 也会将多个数组合并成一个新数组。但关键区别在于:
非数组值:如果两个数组中具有相同字符串键名的元素都不是数组,那么它们会被合并成一个新数组,包含所有这些值。
数组值:如果相同字符串键名的元素都是数组,那么 `array_merge_recursive()` 会递归地合并这些子数组。
数字键:对于数字键名,行为与 `array_merge()` 类似,不会覆盖,而是追加到结果数组中。

3.3 代码示例


示例1:非数组值合并



$array1 = ['color' => 'red', 'size' => 'M'];
$array2 = ['color' => 'blue', 'type' => 'T-shirt'];
$mergedArray = array_merge_recursive($array1, $array2);
print_r($mergedArray);
// 输出: Array ( [color] => Array ( [0] => red [1] => blue ) [size] => M [type] => T-shirt )
// 注意:'color'键的值被合并成了一个新的索引数组

示例2:递归合并子数组



$config1 = [
'database' => [
'host' => 'localhost',
'port' => 3306
],
'app_name' => 'MyApp'
];
$config2 = [
'database' => [
'user' => 'root',
'port' => 3307 // 'port'键重复
],
'app_version' => '1.0'
];
$mergedConfig = array_merge_recursive($config1, $config2);
print_r($mergedConfig);
/* 输出:
Array
(
[database] => Array
(
[host] => localhost
[port] => Array
(
[0] => 3306
[1] => 3307
)
[user] => root
)
[app_name] => MyApp
[app_version] => 1.0
)
*/
// 注意:'database'子数组被递归合并,'port'键的值也合并成了新数组

3.4 注意事项


`array_merge_recursive()` 在处理相同键名时,会将非数组值合并成一个新的索引数组。这可能会导致数据结构变得比预期更复杂,有时并不总是你想要的结果。在使用前应仔细考虑其影响。

四、展开运算符(Spread Operator `...`):现代PHP的优雅

从PHP 7.4开始,我们可以使用展开运算符 `...` 来更简洁地合并数组,它提供了一种类似 `array_merge()` 的行为,但语法更加现代化和易读。

4.1 语法




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

4.2 工作原理


展开运算符的工作原理与 `array_merge()` 非常相似:
索引数组:它会重新索引数字键,将所有元素追加到新数组中。
关联数组:如果存在相同的字符串键名,后出现的键值会覆盖先出现的键值。

实际上,`[...$array1, ...$array2]` 在大多数情况下等同于 `array_merge($array1, $array2)`。

4.3 代码示例


示例1:索引数组合并



$fruits1 = ['apple', 'banana'];
$fruits2 = ['orange', 'grape'];
$allFruits = [...$fruits1, ...$fruits2];
print_r($allFruits);
// 输出: Array ( [0] => apple [1] => banana [2] => orange [3] => grape )

示例2:关联数组合并



$settingsDefault = ['debug' => false, 'log_level' => 'info'];
$settingsUser = ['log_level' => 'warn', 'cache' => true];
$finalSettings = [...$settingsDefault, ...$settingsUser];
print_r($finalSettings);
// 输出: Array ( [debug] => 0 [log_level] => warn [cache] => 1 )
// 'log_level'被覆盖

示例3:与自定义元素混合

展开运算符的另一个优点是,你可以直接在数组字面量中混合展开数组和自定义元素:



$baseOptions = ['width' => 100, 'height' => 200];
$extraOptions = ['color' => 'red'];
$finalOptions = [
'type' => 'box',
...$baseOptions,
'thickness' => 5,
...$extraOptions
];
print_r($finalOptions);
/* 输出:
Array
(
[type] => box
[width] => 100
[height] => 200
[thickness] => 5
[color] => red
)
*/

4.4 何时使用展开运算符?


如果你在PHP 7.4或更高版本开发,并且需要进行简单的数组合并(行为类似于 `array_merge()`),展开运算符是一个极佳的选择,它使代码更简洁、更易读。

五、循环合并:自定义逻辑的终极选择

在某些复杂场景下,上述内置函数可能无法满足你特殊的合并需求,例如,你可能需要在合并过程中进行条件判断、数据转换或自定义冲突解决逻辑。这时,使用循环结构(如 `foreach`)进行手动合并就成了必要的选择。

5.1 示例:条件合并


假设我们有两个用户列表,我们想合并它们,但只添加第二个列表中年龄大于30的用户,并且避免重复的ID。



$users1 = [
['id' => 1, 'name' => 'Alice', 'age' => 25],
['id' => 2, 'name' => 'Bob', 'age' => 35]
];
$users2 = [
['id' => 3, 'name' => 'Charlie', 'age' => 40],
['id' => 1, 'name' => 'AliceX', 'age' => 28], // 重复ID
['id' => 4, 'name' => 'David', 'age' => 29] // 不满足年龄条件
];
$mergedUsers = $users1;
$existingIds = array_column($users1, 'id'); // 提取现有ID
foreach ($users2 as $user) {
// 检查ID是否重复且年龄是否符合条件
if (!in_array($user['id'], $existingIds) && $user['age'] > 30) {
$mergedUsers[] = $user;
$existingIds[] = $user['id']; // 更新现有ID列表
}
}
print_r($mergedUsers);
/* 输出:
Array
(
[0] => Array ( [id] => 1 [name] => Alice [age] => 25 )
[1] => Array ( [id] => 2 [name] => Bob [age] => 35 )
[2] => Array ( [id] => 3 [name] => Charlie [age] => 40 )
)
*/

5.2 优点与缺点



优点:提供了最大的灵活性,可以实现任何复杂的合并逻辑。
缺点:代码相对冗长,可读性可能不如内置函数。对于大型数组,循环操作的性能可能低于经过C语言优化实现的内置函数。

六、性能考量与最佳实践

选择正确的数组合并方法不仅关乎功能实现,也关乎代码的性能和可维护性。

6.1 性能对比



内置函数 (`array_merge`, `+` 运算符, `array_merge_recursive`):这些函数都是在PHP底层(C语言)实现的,经过高度优化。对于大多数场景,它们是处理大型数组时性能最优的选择。
展开运算符 (`...`):在PHP 7.4+ 中,它的性能与 `array_merge()` 相当,因为它在内部被编译为类似的操作。
循环合并:虽然灵活性最高,但由于涉及到用户空间的PHP代码解释和多次函数调用,对于非常大的数组,性能通常会低于内置函数。应仅在确实需要自定义逻辑时使用。

6.2 最佳实践总结



优先使用内置函数:如果 `array_merge()`、`+` 运算符或 `array_merge_recursive()` 能够满足你的需求,总是优先选择它们。它们不仅性能更高,而且代码更简洁、更易于理解。
理解 `array_merge()` 与 `+` 运算符的区别:

需要重新索引数字键,且后出现的关联键值应覆盖先出现的:使用 `array_merge()` 或展开运算符。
需要保留左侧数组的键值(无论是数字还是关联),只添加右侧数组中不存在的键:使用 `+` 运算符。


谨慎使用 `array_merge_recursive()`:当处理多维关联数组时,如果你确实希望将相同键名的非数组值合并成一个新数组,或递归合并子数组,那么它是合适的。但要警惕它可能产生的复杂数据结构。
PHP 7.4+ 推荐使用展开运算符:在支持的环境下,展开运算符是进行简单数组合并的现代化、简洁且性能优异的方式。
自定义逻辑时才使用循环:只有当内置函数无法满足你的特定合并条件(如条件过滤、自定义冲突解决)时,才考虑手动循环合并。
处理空数组和非数组参数:内置函数通常能良好处理空数组(`array_merge` 会忽略空数组,`+` 运算符不会改变结果),但传入非数组参数可能会导致 `TypeError`。在处理不确定类型的变量时,务必进行类型检查,例如使用 `is_array()`。

结语

掌握PHP数组拼接(合并)函数的各种用法及其背后逻辑,是编写高效、健壮PHP代码的关键。从最常见的 `array_merge()` 到独特的 `+` 运算符,再到深度合并的 `array_merge_recursive()` 和现代的展开运算符,每种方法都有其特定的应用场景和行为特点。通过本文的深入解析和代码示例,相信你已能清晰地辨别它们之间的差异,并能够在实际开发中根据需求,自信地选择最合适的工具来处理数组合并任务,从而提升你的编程效率和代码质量。

2026-03-30


上一篇:PHP数据库字符串处理深度解析:安全、编码与最佳实践

下一篇:PHP数据库连接全攻略:从PDO到MySQLi,再到可靠测试与最佳实践