一、先搞清楚它和array_merge()差在哪
PHP内置的array_merge_recursive(),从名字就能看出它和array_merge()是亲戚。它的作用也是把两个或多个数组的元素合并成一个数组,值按顺序追加。这个函数从PHP4.0.1开始就有了,比array_merge()晚一点点。
语法形式:
array array_merge_recursive(array $array1 [, array $...]);
$array1必须传,后面的数组可选,可以传多个。返回值是合并后的数组。
那它和array_merge()到底差在哪?关键就两个字:递归。
上一节讲array_merge()时说过,字符串键冲突时,后面的值直接覆盖前面的。而array_merge_recursive()遇到同样的冲突,不会覆盖,而是把两个值合并成一个数组,都保留下来。这就是它存在的意义。
一句话概括:array_merge()冲突就覆盖,array_merge_recursive()冲突就打包成数组。
二、字符串键冲突:合并成数组
看第一个例子,用代码号学习编程的场景来说明:
<?php
$player1 = array("a" => "sachin", "b" => "virat");
$player2 = array("c" => "yuvraj", "b" => "ganguly");
print_r(array_merge_recursive($player1, $player2));
?>
输出:
Array (
[a] => sachin
[b] => Array ( [0] => virat [1] => ganguly )
[c] => yuvraj
)
b这个键在两个数组里都有。如果是array_merge(),结果会是[b]=>ganguly,virat被丢掉。但array_merge_recursive()把两个值都留下了,b变成了一个包含virat和ganguly的数组。
a和c没有冲突,各自保留原值。
这个行为在什么场景下有用?比如你要统计两份名单里同一个人出现的不同信息,或者合并两组标签时不想丢失任何一项。用array_merge()会丢数据,用array_merge_recursive()能把冲突项都收集起来。
但也要注意,冲突键变成数组后,数据结构就变了。后面如果还按字符串去用这个键,代码可能会出问题。所以用之前要想清楚,这个键变成数组是不是你想要的。
三、键不冲突时:和普通合并没区别
如果两个数组的键没有重叠,array_merge_recursive()的表现和array_merge()一样,就是简单地把值拼在一起。
<?php
$site1 = array("a" => "java", "b" => "T");
$site2 = array("z" => "point", "d" => "tutorial");
print_r(array_merge_recursive($site1, $site2));
?>
输出:
Array ( [a] => java [b] => T [z] => point [d] => tutorial )
四个键a、b、z、d各不相同,没有任何冲突,合并后就是四个键值对平铺在一起。这种情况下,它和array_merge()的结果一致。
所以判断该用哪个函数,取决于你的数据里键有没有可能撞车,以及撞车时你希望怎么处理。键不会冲突,两者随便用;键可能冲突且不想丢数据,用递归版。
四、多个键冲突的情况
当冲突的键不止一个时,每个冲突键都会各自变成数组。
<?php
$color1 = ['A' => 'Black', 'B' => 'Green', 'C' => 'Yellow', 'D' => 'White', 'E' => 'Cyan'];
$color2 = ['F' => 'Aero', 'B' => 'Almond', 'H' => 'Blood', 'D' => 'Coconut', 'J' => 'Dark brown'];
$color = array_merge_recursive($color1, $color2);
print_r($color);
?>
输出:
Array (
[A] => Black
[B] => Array ( [0] => Green [1] => Almond )
[C] => Yellow
[D] => Array ( [0] => White [1] => Coconut )
[E] => Cyan
[F] => Aero
[H] => Blood
[J] => Dark brown
)
B和D都冲突了,各自变成了数组。B里装了Green和Almond,D里装了White和Coconut。其他不冲突的键保持原样。
这里能看出一个规律:冲突的键,值按传入数组的顺序追加。先传的在前,后传的在后。顺序是有保证的,不用担心。
再看一个混合场景:
<?php
$input1 = array("a" => "yuvraj", "b" => "sachin", "c" => "rahul");
$input2 = array("d" => "umesh", "a" => "sachin", "e" => "ganguly");
print_r(array_merge_recursive($input1, $input2));
?>
输出:
Array (
[a] => Array ( [0] => yuvraj [1] => sachin )
[b] => sachin
[c] => rahul
[d] => umesh
[e] => ganguly
)
a在$input1里是yuvraj,在$input2里是sachin,冲突后合并成[0]=>yuvraj[1]=>sachin。注意这里两个值虽然字面不同,但因为是同一个键,照样合并,不会因为值相同就跳过。
我个人的经验是,这种合并结果在输出时要注意格式。如果直接把[a]打印出来,得到的是Array,不是字符串。前端拿到这种结构可能会懵,所以合并后很好做一层处理,或者提前约定好数据结构。
五、数字键和嵌套数组的行为
前面举的例子都是字符串键。那数字键呢?
array_merge_recursive()对数字键的处理和array_merge()类似:数字键的值按顺序追加,键名重新编号。不同的是,如果数字键对应的值本身是数组,递归合并会继续往里走。
比如:
<?php
$a = array("list" => array(1, 2));
$b = array("list" => array(3, 4));
print_r(array_merge_recursive($a, $b));
?>
输出:
Array (
[list] => Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 )
)
list这个键冲突了,但它对应的值是数组,递归合并会把两个数组的内容继续往下合并,而不是把两个数组当成两个元素打包。这是"递归"两个字的真正体现:遇到数组就继续往下走,直到不能再分。
这个特性在处理多层配置、嵌套数据结构时比较实用。但也容易让结果变得比预期复杂,用之前很好先想清楚最终结构长什么样。
六、和array_merge()、+运算符的选型
三个工具摆在一起,怎么选?
array_merge():字符串键冲突时后者覆盖前者,数字键重排。适合"新数据覆盖旧数据"的场景,比如配置合并、参数覆盖。
array_merge_recursive():字符串键冲突时合并成数组,数字键重排,嵌套数组继续递归。适合"冲突数据都要保留"的场景,比如合并多来源的标签、汇总多份名单。
+运算符:键冲突时保留左边的,忽略右边的,数字键不重排。适合"前面的数据优先"的场景。
我的建议是,先想清楚冲突时你要的是什么结果,再选函数。不要因为array_merge_recursive()名字长就觉得它更高级,它只是行为不同。用错了,数据结构会变得难以预期。
还有一点,array_merge_recursive()在PHP8.0之后对类型的要求更严格了,传非数组参数会报TypeError。老代码迁移时要注意这个变化。
七、本节课程知识要点
-
array_merge_recursive()递归合并一个或多个数组,返回新数组。 -
字符串键冲突时,不覆盖,而是把值合并成数组。
-
键不冲突时,表现和
array_merge()一致。 -
冲突键的值按传入顺序追加,先传的在前。
-
数字键按
array_merge()的规则重排编号。 -
嵌套数组会继续递归合并,不是简单打包。
-
想保留冲突数据用递归版,想覆盖用
array_merge(),想保留左边用+。 -
合并后数据结构可能变化,后续取值要留意键是否已变成数组。