php数组合并的二种方法 前言「把两个数组合成一个」在 PHP 里有两套写法array_merge()函数和数组联合运算符。很多文章把它们写成「差不多随便挑一个」这是本文要纠正的第一个误解——它们处理键的规则完全相反array_merge()是「后面的覆盖前面的、数字键全部重新编号」是「左边的优先、一个键都不动」。选错了症状往往不是报错而是数据被悄悄覆盖或下标莫名其妙地变了非常难查。第二个常见误解是「合并就是array_merge拆分就是array_slice」。PHP 在合并与拆分这件事上其实给了好几把不同的工具各自对键的处理规则都不一样array_merge_recursive()会在字符串键冲突时递归合并array_replace()是「后者覆盖前者但保留键」array_slice()默认重置数字键array_splice()会直接改掉原数组。本文先讲清两种合并方法的规则差异再给出三种典型场景该怎么选最后把拆分方向的array_chunk()、array_slice()、array_splice()、array_combine()一次讲完。文中所有示例都可以直接复制运行基准版本是 PHP 8.0 及以上涉及版本差异的地方会单独标注。一、array_merge()字符串键后者覆盖数字键重新编号array_merge(array ...$arrays): array的规则只有两条但它们组合起来的效果很反直觉字符串键冲突时后面的值覆盖前面的值位置不变值被换掉。数字键不参与覆盖一律按顺序追加并从 0 开始重新编号。?php // 适用于 PHP 7.4declare(strict_types1);$a [name 张三, age 30, 0 a, 1 b];$b [name 李四, city 北京, 0 c];print_r(array_merge($a, $b));// Array// (// [name] 李四 - 字符串键被 $b 覆盖// [age] 30// [0] a - $a 的数字键从 0 重新编号// [1] b// [city] 北京// [2] c - $b 的数字键继续往下编号// )print_r($a $b);// Array// (// [name] 张三 - 字符串键保留 $a 的// [age] 30// [0] a - 数字键也保留 $a 的// [1] b// [city] 北京 - 只在 $a 里不存在的键才会补进来// )注意上面那两段输出里最容易被忽略的一点array_merge()里$b的0 c并没有覆盖$a的0 a它变成了2 c。这就是「数字键重新编号」的实际含义——如果你用数字键当业务主键订单号、用户 ID 之类array_merge()会把它们全部破坏掉。这里还要注意一个陷阱数字字符串键也不例外。键0、8、100会被 PHP 当成整数键这是数组键的强制转换规则因此同样会被array_merge()重新编号。把整数塞进字符串键里并不能保住它们。array_merge_recursive()则不同当两个数组在同一个字符串键上都有值、且都是数组时它会把它们递归合并如果都是非数组的标量则把两个值合并成一个数组。数字键的处理与array_merge()一样是追加。?php // 适用于 PHP 7.4$default [db [host localhost], tags [php], mode dev];$custom [db [port 3306], tags [web], mode prod];print_r(array_merge_recursive($default, $custom));// [db] [host localhost, port 3306] - 递归合并// php,开发语言 [0 php, 1 web] - 数字键追加// [mode] [0 dev, 1 prod] - 标量冲突变成数组最后那个mode [dev, prod]就是array_merge_recursive()的坑它不会「用新的覆盖旧的」而是把两个值都留下来于是原本的字符串变成了数组下游代码拿到之后往往直接崩。二、 运算符左边优先一个键都不动是数组联合union运算符规则只有一条以左边的数组为基准把右边数组中「键在左边不存在」的元素补进来键相同的左边的赢。它和array_merge()的关键差别在数字键上不会重新编号也不会把右边同键的元素追加进去而是直接丢弃右边的同名元素。?php // 适用于 PHP 7.0$left [0 a, 1 b, k L];$right [0 c, 2 d, k R];print_r($left $right);// [0] a - 左边优先右边的 0 c 被丢弃// [1] b// [k] L - 同上// [2] d - 只在右边存在的键才补进来print_r(array_merge($left, $right));// [0] a// [1] b// [k] R - 字符串键被右边的覆盖// [2] c - 数字键被重新编号追加// [3] d把两段输出对照着看两种方法的差异一目了然场景array_merge()字符串键冲突后者覆盖前者左边保留右边丢弃数字键冲突不冲突被重新编号后全部保留左边保留右边丢弃数字键最终下标从 0 起连续重排原样不动元素顺序依次遍历每个数组按数组顺序追加左边的顺序在前右边新增的在后元素丢失不丢字符串键的值会被覆盖会丢右边同键的元素被丢弃传入非数组PHP 8.0 起抛TypeError不适用运算符只接受数组无参数调用PHP 7.4 起返回空数组此前至少要传一个参数不适用三、该用哪个三种典型场景场景一合并配置文件希望「用户配置覆盖默认配置」。这时要的是「后者覆盖前者」但又不希望数字键被重排。用array_replace()?php // 适用于 PHP 7.0$defaults [host localhost, port 3306, charset utf8mb4];$user [host db.internal, port 3307];$config array_replace($defaults, $user);print_r($config);// [host] db.internal - 后面的覆盖前面的// [port] 3307// [charset] utf8mb4 - 未冲突的键保留array_replace(array $array, array ...$replacements): array的取值规则与相反右边优先但与一样保留原有键、不重排数字键。它和$user $defaults取到的值相同差别在顺序array_replace()保持第一个数组的元素顺序则把左操作数的元素排在前面。做配置合并时顺序稳定通常更重要所以优先用array_replace()。场景二合并两个列表不关心原来的下标。例如把两组待处理 ID 拼成一个队列用array_merge()?php // 适用于 PHP 7.4$pending [101, 102, 103];$retry [102, 104]; // 注意 102 重复了$queue array_merge($pending, $retry);// [101, 102, 103, 102, 104] —— 下标连续重复值不会自动去重$unique array_values(array_unique($queue));// [101, 102, 103, 104]这里要记住array_merge()不去重。想按值去重用array_unique()想按「是不是同一个 ID」去重用键来承载语义$map[$id] true比用值更可靠。场景三合并两个以 ID 为键的映射保留 ID。这时候array_merge()会把 ID 全部重排会丢掉右边同名 ID 的新数据两个都不合适应该自己控制覆盖方向?php // 适用于 PHP 7.0/** param arrayint,string $old param arrayint,string $new */function mergeById(array $old, array $new): array{// 后者优先先放新的再用「旧数组中新增部分」补齐return $new $old;}$old [7 旧名称, 9 未变];$new [7 新名称, 11 新增];print_r(mergeById($old, $new));// [7] 新名称 - 新数据优先// [11] 新增// [9] 未变 - 左边没有的键补进来注意array_merge()在没有参数时返回空数组PHP 7.4 起才允许无参调用之前的版本至少要传一个参数。所以array_merge(...$arrays)这种展开写法在数组为空时是合法的条件是 PHP 7.4 及以上。四、拆分方向四个常用函数合并讲完把拆分方向的四个函数一次交代清楚。它们的共同问题是「键怎么处理」而这恰恰是手册里最容易看漏的部分。array_chunk(array $array, int $length, bool $preserve_keys false): array—— 按长度切块返回二维数组外层从 0 开始编号。?php // 适用于 PHP 8.0$ids range(1, 7);print_r(array_chunk($ids, 3));// [[1,2,3], [4,5,6], [7]]$assoc [a 1, b 2, c 3];print_r(array_chunk($assoc, 2, true)); // 第三参数 true保留原键// [[a 1, b 2], [c 3]]$length小于 1 时PHP 8.0 起抛ValueError更早的版本是E_WARNING并返回null。array_slice(array $array, int $offset, ?int $length null, bool $preserve_keys false): array—— 取一段不修改原数组。?php // 适用于 PHP 7.0$arr [10 a, 20 b, k c, 30 d];print_r(array_slice($arr, 1, 2));// [0] b - 整数键被重置为 0 起编号// [k] c - 字符串键始终保留print_r(array_slice($arr, 1, 2, true));// [20] b - 传 true 才保留整数键// [k] c规则可以记成一句字符串键永远保留整数键默认重置除非第四个参数传true。array_splice(array $array, int $offset, ?int $length null, mixed $replacement []): array—— 从原数组中「剪掉」一段并可选地插入新内容返回值是被剪掉的那部分。注意它接收的是引用原数组会被直接修改。?php // 适用于 PHP 7.0$input [red, green, blue, yellow];$removed array_splice($input, 1, 2, [black, maroon]);print_r($input);// [red, black, maroon, yellow] - 原数组被改掉了print_r($removed);// [green, blue] - 返回值是被删掉的部分关于键手册明确承诺的只有两条输入数组的数字键不保留替换数组的键也不保留。至于输入数组里字符串键在array_splice()之后是什么行为手册没有给出承诺不要依赖。要在关联数组上做「按位置插入」稳妥做法是先用array_keys()定位、再用array_slice()拼接而不是猜array_splice()的字符串键行为。array_combine(array $keys, array $values): array—— 把两个等长数组拼成「第一个数组的值当键、第二个数组的值当值」。?php // 适用于 PHP 8.0$fields [host, port, user];$vals [localhost, 3306, root];print_r(array_combine($fields, $vals));// [host localhost, port 3306, user root]// 两个数组长度不一致时try {array_combine($fields, [localhost]); // PHP 8.0 抛 ValueError} catch (ValueError $e) {echo 长度不一致 . $e-getMessage() . \n;}在PHP 8.0 之前长度不一致只会发一条E_WARNING并返回falsePHP 8.0 起改为抛ValueError。所以「先把返回值当false判断」的老代码在新版本上会直接中断。另外键数组里如果出现重复值后面的会覆盖前面的。常见坑点❌ 用array_merge()合并以数字 ID 为键的数组期望保留下标。✅ 数字键会被从 0 开始重新编号数字字符串键也一样此时应改用或array_replace()。❌ 用$old $new合并配置以为新的会覆盖旧的。✅是左边优先$old里已存在的键不会被$new覆盖要「后者覆盖前者」用array_replace($old, $new)。❌ 以为array_merge()对同名字符串键会「合并内容」。✅array_merge()是同名键直接覆盖会做递归合并的是array_merge_recursive()而它会把标量冲突变成数组。❌ 把null或字符串直接传给array_merge()。✅ PHP 8.0 起抛TypeError此前是E_WARNING并返回null先确认参数都是数组。❌ 给array_chunk()传$length 0却没有处理返回值。✅ 小于 1 时 PHP 8.0 起抛ValueError先自行校验长度再调用。❌ 用array_slice()取关联数组的一段发现整数键「变了」。✅ 整数键默认被重新编号需要保留就传第四个参数true字符串键无论如何都保留。❌ 为拿返回值而调用array_splice()忘了它会修改原数组。✅ 它第一个参数是按引用传递的原数组会被改写只想取片段、不动原数组就用array_slice()。❌ 用if (array_combine($k, $v) false)处理两个数组长度不等的情况。✅ PHP 8.0 起抛ValueError而不是返回false要么先count()比较要么用try/catch接住。总结需求推荐键的处理合并列表下标无所谓array_merge()字符串键后者覆盖数字键从 0 重排合并配置后者覆盖前者array_replace()键全部保留右边同键的值胜出合并映射左边优先键全部保留左边同键的元素胜出深层合并配置array_merge_recursive()字符串键递归合并数字键追加标量冲突变成数组按长度切块array_chunk()默认重排数字键第三参数为true时保留取一段但不动原数组array_slice()字符串键保留整数键默认重排剪一段并就地插入array_splice()手册只承诺数字键与替换键不保留字符串键未作承诺两数组合成键值对array_combine()长度不等时 PHP 8.0 起抛ValueError选哪种合并方式先问自己一个问题「键到底有没有业务含义」如果键只是下标用array_merge()最省事如果键是 ID、配置项名、字段名就绝对不能让它被重排或覆盖此时与array_replace()才是安全的两者的区别只在于「谁优先」和「元素顺序」。至于拆分方向记住array_slice()不改原数组、array_splice()改原数组且字符串键行为未作承诺基本就不会踩到最疼的那几个坑了。