一、array_merge()的定位
PHP内置的array_merge()函数,用来把一个或多个数组的元素合并成一个数组。它从PHP4.0开始就有了,是数组操作里出场率比较高的一个。
语法形式:
array array_merge(array $array1 [, array $...]);
$array1是必须的,后面可以跟任意多个数组,都是可选的。返回值就是合并后的数组。
这个函数的用法看着简单,但真正容易出问题的地方在键名处理上。很多人第一次用array_merge()踩坑,都是因为没搞清楚字符串键和数字键在合并时的行为不一样。
官方文档给了一句提示:如果两个或多个数组元素有相同的键,之后一个会覆盖前面的。这句话只说对了一半,准确说,这条规则只适用于字符串键。数字键是另一套逻辑,下面会分开讲。
二、字符串键:后面的覆盖前面的
先看字符串键的情况,用代码号学习编程的示例来说明:
<?php
$game1 = array("a" => "cricket", "b" => "hockey");
$game2 = array("c" => "football", "b" => "tennis");
print_r(array_merge($game1, $game2));
?>
输出:
Array ( [a] => cricket [b] => tennis [c] => football )
两个数组都有b这个键。$game1里b是hockey,$game2里b是tennis。合并后b的值变成了tennis,也就是后一个数组的值把前一个覆盖掉了。a和c没有冲突,各自保留。
这就是字符串键的规则:键名相同就覆盖,位置靠后的赢。
项目开发里这个特性有它的用处。比如你有一个默认配置数组,再传一个用户配置数组进来,用array_merge($default,$user)就能让用户配置覆盖默认值,写法很干净。这是array_merge()在配置合并场景下的典型用法,比手写循环判断要省事。
但反过来,如果你不希望覆盖,就得换别的函数,比如array_merge_recursive(),它会把冲突的键值合并成数组,而不是简单覆盖。这两个函数的差异,选型时要想清楚。
三、数字键:不覆盖,重新编号
数字键的行为和字符串键不同。看这个例子:
<?php
$a = [1, 2, 3, 4];
$b = [5, 6, 7, 8];
print_r(array_merge($a, $b));
?>
输出:
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6 [6] => 7 [7] => 8 )
两个数组的键都是0、1、2、3,按理说会冲突。但合并后并没有覆盖,而是把$b的值接在$a后面,键从0开始重新编号,一直排到7。
这就是数字键的规则:不覆盖,重新索引。array_merge()会把所有数字键的值按顺序追加,然后从0开始重新分配键名。
再看一个键名不连续的例子:
<?php
$a1 = array(1 => "java", 6 => "T", 4 => "point");
$res = array_merge($a1);
print_r($res);
?>
输出:
Array ( [0] => java [1] => T [2] => point )
原本键是1、6、4,合并后变成0、1、2。哪怕只传一个数组,数字键也会被重新编号。这一点容易忽略,如果你合并后还要按原来的键取值,就会出问题。
我个人的经验是:处理索引数组时,把array_merge()当成"拼接并重排"来看,不要指望它保留原来的键。如果键名有意义、必须保留,那就得用+运算符或者array_replace(),它们的行为和array_merge()不一样。
四、混合键名的合并
实际数据里,字符串键和数字键经常混在一起。看这个例子:
<?php
$cric1 = array("sachin" => "18426", 15921, 200);
$cric2 = array("virat", "ganguly", "sachin" => "arjun", "yuvraj" => "world cup", 2011);
$res = array_merge($cric1, $cric2);
print_r($res);
?>
输出:
Array (
[sachin] => arjun
[0] => 15921
[1] => 200
[2] => virat
[3] => ganguly
[yuvraj] => world cup
[4] => 2011
)
拆开看这个结果:
sachin是字符串键,两个数组都有,后面的arjun覆盖了前面的18426。
yuvraj是字符串键,只在$cric2里有,直接保留。
15921和200在$cric1里是数字键,virat、ganguly、2011在$cric2里也是数字键。这些数字键的值合并后按顺序重排,键从0开始分配。
所以最终结果里,字符串键sachin和yuvraj保留了自己的键名,数字键全部重排成0到4。
这个例子比较典型地展示了array_merge()的完整行为。理解了它,基本就掌握了这个函数的核心。
五、和+运算符的区别
说到数组合并,绕不开+运算符。两者经常被拿来比较,但行为差别不小。
+运算符的规则是:键名相同就保留左边的,忽略右边的。它不看键是字符串还是数字,一视同仁。
<?php
$a = array("x" => 1, "y" => 2);
$b = array("x" => 3, "z" => 4);
print_r($a + $b);
?>
输出:
Array ( [x] => 1 [y] => 2 [z] => 4 )
x冲突了,+保留了$a里的1,而不是$b里的3。这和array_merge()的"后者覆盖"正好相反。
所以选择的时候要看你的意图:
想让后面的数组覆盖前面的,用array_merge()。
想让前面的数组优先、不被覆盖,用+。
需要保留数字键原样,用+或者array_replace()。
我见过不少代码用+来合并配置,其实意图是"用户配置覆盖默认配置",结果因为+的规则反了,默认值把用户值挡住了。这类bug不报错,只是结果不对,排查起来挺费劲。所以合并配置时,我一般倾向于明确用array_merge()。
六、本节课程知识要点
-
array_merge()合并一个或多个数组,返回新数组。 -
字符串键冲突时,后面的覆盖前面的。
-
数字键不覆盖,按顺序追加后从0重新编号。
-
哪怕只传一个数组,数字键也会被重新编号。
-
混合键名时,字符串键按覆盖规则处理,数字键按重排规则处理。
-
+运算符保留左边的值,和array_merge()的覆盖方向相反。 -
配置合并想要覆盖效果,用
array_merge();想保留原键,考虑+或array_replace()。 -
需要递归合并冲突键时,看
array_merge_recursive()。