2026年10月3日 · 约 6 分钟
文档里对三种数组对比方法各有一段简介。用来选一个够用——但不足以预测 diff 返回时你到底会看到什么。这篇文章把同一份数据分别用三种方法跑一遍,展示每种方法的精确输出,因为它们的差异比名字听起来大得多。
先快速回顾机制(详见引擎原理):
a[i] 和 b[i] 配对,逐对递归。同一个选项,三种截然不同的答案。
一条事件日志在位置 1 新增了一条记录:
{ "items": [1, 2, 3, 4] }
{ "items": [1, 5, 2, 3, 4] }
By Index 发现位置对齐被打乱,报告出一串级联:
valueChanged items[1]
valueChanged items[2]
valueChanged items[3]
added items[4]
一次插入,四条差异——技术上正确,实际上没用。插入点之后的一切都「变了」。
LCS 找到公共子序列 [1, 2, 3, 4],精确报告发生的事:
added items[1]
Unordered 同样命中——base 的每个元素都找到了匹配,contrast 侧剩下一个:
added items[1]
胜者:LCS 或 Unordered。除非位置本身真的有意义,否则 By Index 在这里只会制造噪声。
同样的元素,不同的顺序:
{ "items": [1, 2, 3] }
{ "items": [3, 1, 2] }
By Index 把每个位置都报告为变更:
valueChanged items[0]
valueChanged items[1]
valueChanged items[2]
LCS 保住它能保住的最长有序片段([1, 2]),剩下的报告为移动:
deleted items[2]
added items[0]
Unordered 什么都没说:
(无差异)
哪个答案正确完全取决于你的数据。部署流水线里步骤换了顺序?By Index 的警报是对的——顺序就是语义。博客文章的标签换了顺序?Unordered 的沉默是对的。这个场景最容易因为选错方法而要么被噪声淹没,要么漏掉真正的回归。
这是文档没有明说的取舍。数组里一个对象的字段变了:
{ "users": [{ "id": 1, "role": "admin" }, { "id": 2, "role": "user" }] }
{ "users": [{ "id": 1, "role": "admin" }, { "id": 2, "role": "owner" }] }
By Index 递归进这一对,精确到字段:
valueChanged users[1].role
LCS 按深度相等匹配元素——而 { "id": 2, "role": "user" } 和 { "id": 2, "role": "owner" } 并不深度相等。于是这个元素整个掉出公共子序列:
deleted users[1]
added users[1]
Unordered 的表现一样:
deleted users[1]
added users[1]
同一个索引,但没有任何关于内部什么变了的线索。如果你的数组里是对象、而且你关心字段级的变化,LCS 和 Unordered 会丢失信息——它们说不出「role 从 user 变成了 owner」,只能说「[1] 处少了点什么、多了点什么」。By Index 是唯一能保住字段级精度的方法。
Unordered 常被描述成「集合对比」,但实现是多重集合语义:每个匹配一对一消耗。
{ "tags": [1, 2, 2, 3] }
{ "tags": [3, 1, 2] }
左边的两个 2 只能消耗掉右边的一个 2:
deleted tags[2]
如果重复次数对你的数据有意义,这正是你想要的行为。如果你真的想要集合语义(「唯一值相同即可」),应该在对比前去重——或者清楚 Unordered 会把数量差异报告出来。
三种方法的成本也不同,在大数组上会很明显:
| 方法 | 时间复杂度 | 说明 |
|---|---|---|
| By Index | O(n) 次比较 | 每对再各加一次递归比较 |
| LCS | O(m × n) 次深度相等判断 | DP 的每个格子都可能递归比较两棵完整的子树 |
| Unordered | 最坏 O(m × n) | 每个未匹配的 base 元素都要扫描 contrast 侧 |
一百个元素的数组,怎么选都无感。但对一万个嵌套对象的数组,LCS 的 DP 表——每个格子一次完整递归比较——是真的贵,Unordered 的平方扫描也好不到哪去。By Index 线性增长,是唯一在任何规模下都几乎免费的方法。
| 你的数组是… | 最佳方法 | 你放弃了什么 |
|---|---|---|
| 位置语义(元组、时间序列、表格行) | By Index | 插入会导致整体偏移——错位列表上全是噪声 |
| 有序但偶尔增删(日志、更新记录) | LCS | 修改元素的字段级精度;O(m·n) 成本 |
| 无序集合(标签、权限、开关) | Unordered | 顺序变化不可见;只能报告整个元素的增删 |
一句话总结:By Index 精确但脆弱,LCS 精简但肤浅,Unordered 平静但对顺序失明。
拿你自己的数组试试——方法开关就在设置里,切换后几秒钟就能重跑一次对比:立即对比两个 JSON 文件。