在PHP数据整理过程中,数组去重是最常见的操作之一。对于一维数组,内置函数array_unique可以轻松搞定,可一旦数组结构变复杂,比如数组中嵌套着数组,每条记录包含多个字段,array_unique就会失效,因为无法直接比较两个子数组是否相等。本文将围绕多维数组的重复结构剔除问题,重点讲解序列化比较法的原理与实现,并对比其他常见方案,帮助你选对工具。

为什么array_unique无法处理多维数组
先来看一个典型场景:从数据库或接口取回的商品列表中,每条记录是一个关联数组,包含id、名称、价格等字段,由于多表关联或多次请求合并,列表里出现了完全重复的记录。很多人第一反应是套用array_unique,结果发现返回的数据要么没有变化,要么出现意料之外的键位缺失。
问题的根源在于array_unique的比较机制。该函数默认使用字符串方式比较元素值,当元素本身是数组时,PHP会发出通知并把它当作无法比较的值处理,结果是只有第一个数组元素被保留,后续所有数组元素无论是否重复都被判定为“不相等”或被直接忽略,行为并不符合预期。而且array_unique比较的是“值”而非“结构”,如果两个子数组的键顺序不同但内容一致,从业务角度看可能是重复数据,函数却无法识别。
因此,要正确剔除多维数组中的重复结构,需要先把每个子数组转换成可比较的标量形式,这正是序列化比较法的核心思路。
序列化比较法的原理与实现
序列化比较法的思路非常直观:serialize函数能把任意PHP变量(包括数组)转换成字符串,两个结构完全相同的数组序列化后的字符串必然相同。利用数组键的唯一性,把序列化字符串当作临时键名写入一个中间数组,重复的记录会自动被覆盖,最后取出值即可完成去重。
基础实现如下:
function uniqueMultiArray(array $array): array
{
$temp = [];
foreach ($array as $item) {
$key = serialize($item);
$temp[$key] = $item; // 重复结构会覆盖前一条,最终只保留最后一条
}
return array_values($temp); // 重建数字索引
}
$data = [
['id' => 1, 'name' => 'apple', 'price' => 8.5],
['id' => 2, 'name' => 'banana', 'price' => 3.2],
['id' => 1, 'name' => 'apple', 'price' => 8.5], // 与第一条完全相同
['id' => 3, 'name' => 'cherry', 'price' => 12.0],
];
$result = uniqueMultiArray($data);
print_r($result); // 输出3条不重复记录如果希望保留的是第一次出现的记录而不是最后一次,只需在写入前判断键是否已存在,用isset($temp[$key])做一次检查即可。这个小细节在实际业务中经常用到,比如日志合并时通常保留最早的数据。
需要注意的是,serialize对键的顺序敏感。['a'=>1,'b'=>2]和['b'=>2,'a'=>1]序列化结果不同,会被视为两条不同记录。如果业务上认为键顺序无关紧要,可以先对每个子数组执行ksort再序列化,保证比较的一致性:
function uniqueMultiArrayStable(array $array): array
{
$temp = [];
foreach ($array as $item) {
if (is_array($item)) {
ksort($item); // 按键名排序,消除键顺序差异
}
$temp[serialize($item)] = $item;
}
return array_values($temp);
}这种写法时间复杂度是O(n),对于上万条记录也能在毫秒级完成,远优于双重循环逐条比较的O(n²)方案。
其他去重方案对比与选择建议
除了序列化,还有几种常见做法。第一种是使用json_encode替代serialize生成比较键,优点是字符串更短、可读性更好,缺点是编码同样受键顺序影响,且遇到资源类型或对象时表现不如序列化稳定。第二种是自定义回调比较,逐字段判断相等,灵活度最高,比如只想按id字段去重,忽略其他字段差异:
function uniqueByField(array $array, string $field): array
{
$temp = [];
foreach ($array as $item) {
$temp[$item[$field]] = $item; // 以指定字段值为键
}
return array_values($temp);
}
// 按id去重,即使name和price不同也算重复
$result = uniqueByField($data, 'id');第三种是递归去重,适用于子数组内部还嵌套数组的深层结构,通过递归调用对每一层分别处理,实现上稍复杂但覆盖面广。三种方案各有侧重:完全结构去重首选序列化比较法,性能与准确性兼顾;按业务字段去重用字段键值法最简洁;超深层嵌套结构才需要考虑递归方案。
还有一个容易踩的坑:序列化字符串可能包含双引号、冒号、分号等字符,直接当作键虽然合法,但在调试输出时非常难读。如果需要输出中间结果排查问题,可以对序列化字符串再执行一次md5得到定长短哈希作为键,既不影响去重逻辑,又便于观察。此外,若数据来源不可信,反序列化存在对象注入风险,去重场景中只做serialize不做unserialize则完全安全,可以放心使用。
总结一下,多维数组去重的关键在于把“结构比较”转化为“标量比较”,序列化比较法借助PHP数组键的唯一性,用最少的代码实现了高效准确的去重效果。结合按键排序和字段键值法等技巧,基本可以覆盖日常数据整理中的各类重复结构剔除需求。