array_unique 默认重排键名是设计使然,因其实现基于值哈希后构建新数组,天然丢弃原键;如需保留键名,须手动遍历并用 $seen 数组标记已见值,避免 array_flip 等陷阱。

array_unique 为什么默认会重排键名
array_unique 的设计逻辑是「去重后返回新数组」,它不承诺保留原始键名——哪怕你传的是关联数组。底层实现会先用值做哈希比对,再把结果塞进一个新数组里,这个过程天然丢弃原键。所以你看到 0 => 'a', 2 => 'b' 变成 0 => 'a', 1 => 'b',不是 bug,是预期行为。
常见错误现象:
• 用 array_unique($arr) 后,foreach ($arr as $k => $v) 里的 $k 不再对应原始数据位置
• 依赖键名做后续映射(比如数据库 ID 当键)时,逻辑直接错乱
- 如果只是要保留键名、不关心顺序,用
array_values+array_unique组合没用——它照样重排 - 真正要保留键名,必须自己遍历去重,不能依赖
array_unique的默认输出 - PHP 8.3+ 仍未提供原生参数控制键名保留,别等了
手动去重并保留键名的可靠写法
核心思路:遍历原数组,用值作键存进临时数组,遇到重复值就跳过,最后用 array_keys 和 array_intersect_key 拿回原始键值对。比用 array_flip 更安全,避免值为非字符串/数字时出错。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
$seen = []记录已出现的值,判断!isset($seen[$v]),而不是!in_array($v, $seen)——后者是 O(n) 复杂度,大数组明显卡顿 - 如果值可能为数组或对象,
array_unique本身就不支持,必须提前序列化或自定义 hash 规则 - 示例代码:
$arr = ['a' => 1, 'b' => 2, 'c' => 1, 'd' => 3];
$seen = [];
$result = [];
foreach ($arr as $k => $v) {
if (!isset($seen[$v])) {
$seen[$v] = true;
$result[$k] = $v;
}
}
运行后 $result 是 ['a' => 1, 'b' => 2, 'd' => 3],键名完全保留。
用 array\_flip + array\_flip 组合的陷阱
网上常见写法:array_flip(array_flip($arr)),看似能“还原键名”,但实际风险很大。
问题根源:
• 第一次 array_flip 会把原数组的值当新键,若值非字符串/整数(比如 null、false、浮点数 1.0),会被强制转换或丢失
• 若原数组有重复值,array_flip 会直接覆盖,你根本不知道哪个键被留、哪个被删
- 例如
['a' => 1, 'b' => 1.0]经过两次array_flip后只剩一个元素,且键名可能是1或1.0,不可控 - 如果值含中文、emoji 或特殊字符,
array_flip在某些 PHP 版本下会静默失败 - 性能上多一次完整遍历,无必要开销
处理多维数组或对象时的替代方案
array_unique 对多维数组直接失效,报 Warning 并返回原数组;对对象数组,默认按 __toString 或资源 ID 去重,基本不可用。
正确做法取决于场景:
- 二维关联数组(如数据库记录),按某字段去重:用
array_column($arr, 'id')提取字段,再套用前面的手动去重逻辑 - 需要深比较(比如整个子数组内容一致才算重复):先
json_encode每个子项,再按字符串去重,但要注意浮点精度和键序影响 - 对象数组:实现
JsonSerializable接口,或用spl_object_id辅助判断,别直接扔给array_unique
最常被忽略的一点:去重逻辑是否该考虑大小写、空白符、类型强制转换。比如 '1' 和 1 算不算重复?这没法靠函数自动判断,得在 foreach 里显式处理。










