2026年10月3日 · 约 7 分钟
文档里对重复元素的三种对比方法各有一段简介。用来选一个够用——但不足以预测 diff 实际会说什么。这篇文章把同一份 XML 用三种方法各跑一遍,展示精确输出,因为它们的差异比名字听起来大得多。
先用一句话把 XML 和机制接上(详见 XML diff 原理):重复的兄弟元素——同一个 <log> 下的三个 <event>——在数据树里会变成数组。你选的这个选项,作用的正是这些数组:
event[0] 和 event[0] 配对,然后递归。每个父节点下只出现一次的元素按名字匹配,所以这个选项只在同名标签重复出现时才有影响。
一条日志在第二个位置新增了一条事件:
<log><event>1</event><event>2</event><event>3</event><event>4</event></log>
<log><event>1</event><event>5</event><event>2</event><event>3</event><event>4</event></log>
By Index 发现位置对齐被打乱,报告出一串级联:
valueChanged log.event[1]
valueChanged log.event[2]
valueChanged log.event[3]
added log.event[4]
一次插入,四条差异。LCS 锁定公共子序列,精确说出发生了什么:
added log.event[1]
Unordered 结论相同:
added log.event[1]
同样的步骤,不同的顺序:
<steps><step>build</step><step>test</step><step>deploy</step></steps>
<steps><step>deploy</step><step>build</step><step>test</step></steps>
By Index 把每个位置都标记出来:
valueChanged steps.step[0]
valueChanged steps.step[1]
valueChanged steps.step[2]
LCS 保住它能保住的最长有序片段,剩下的报告为移动:
deleted steps.step[2]
added steps.step[0]
Unordered 保持沉默:
(无差异)
哪个答案正确?如果这是 Ant 构建文件或 WS-Security 头这种元素顺序有契约意义的地方,By Index 的警报才是对的。如果只是一堆 <tag> 或 <permission>,Unordered 的沉默才是对的。选错方法,要么被噪声淹没,要么漏掉真正的回归。
这是文档没有明说的取舍。重复块里一个子元素变了:
<users>
<user id="1"><role>admin</role></user>
<user id="2"><role>user</role></user>
</users>
<users>
<user id="1"><role>admin</role></user>
<user id="2"><role>owner</role></user>
</users>
By Index 递归进这一对,精确到具体位置——属性、子元素,全都覆盖:
valueChanged users.user[1].role
LCS 按深度相等匹配元素,而第二个 <user> 已经不再符合。整个元素掉出公共子序列:
deleted users.user[1]
added users.user[1]
Unordered 报告同样的一对。你知道了 user[1] 不同,但不知道变的是 id 属性、<role> 还是别的什么。对于结构化的重复元素——配置里的 <server> 块、schema 里的 <field> 定义——By Index 是唯一能保住字段级精度的方法。
Unordered 是多重集合匹配,不是集合匹配——每个匹配一对一消耗:
<post><tag>a</tag><tag>b</tag><tag>b</tag><tag>c</tag></post>
<post><tag>c</tag><tag>a</tag><tag>b</tag></post>
左边两个 <tag>b</tag>,右边只有一个:
deleted post.tag[2]
如果重复次数对你的数据有意义,这正是你要的行为。如果你真的想要「唯一值相同即可」,应该先自行去重——或者接受 Unordered 会把数量差异报告出来。
| 方法 | 时间复杂度 | 说明 |
|---|---|---|
| By Index | O(n) 次比较 | 每对再各加一次递归比较 |
| LCS | O(m × n) 次深度相等判断 | DP 的每个格子都可能递归比较两棵完整的子树 |
| Unordered | 最坏 O(m × n) | 每个未匹配的 base 元素都要扫描 contrast 侧 |
二十个 <server> 的配置文件,怎么选都是瞬间完成。但对上万个 <record> 的数据导出,LCS 的表——每个格子一次完整递归比较——是真的贵,Unordered 的平方扫描也好不到哪去。By Index 是唯一在任何规模下都几乎免费的方法。
| 你的重复元素是… | 最佳方法 | 你放弃了什么 |
|---|---|---|
| 位置语义(流水线步骤、表格行、坐标) | By Index | 插入会导致整体偏移——错位列表上全是噪声 |
| 有序但偶尔增删(日志、更新记录) | LCS | 修改元素的字段级精度;O(m·n) 成本 |
| 无序集合(标签、权限、开关) | Unordered | 顺序变化不可见;只能报告整个元素的增删 |
一句话总结:By Index 精确但脆弱,LCS 精简但肤浅,Unordered 平静但对顺序失明。
拿你自己的文档试试——方法开关就在设置里,切换后几秒钟就能重跑对比:立即对比两个 XML 文档。