指南

XML 重复元素是怎么对比的:By Index、LCS、Unordered 实战

2026年10月3日 · 约 7 分钟

文档里对重复元素的三种对比方法各有一段简介。用来选一个够用——但不足以预测 diff 实际会说什么。这篇文章把同一份 XML 用三种方法各跑一遍,展示精确输出,因为它们的差异比名字听起来大得多。

先用一句话把 XML 和机制接上(详见 XML diff 原理):重复的兄弟元素——同一个 <log> 下的三个 <event>——在数据树里会变成数组。你选的这个选项,作用的正是这些数组:

  • By Index 把 event[0] 和 event[0] 配对,然后递归。
  • LCS 找出深度相等元素的最长公共子序列,其余的报告为插入或删除。
  • Unordered 把这些兄弟元素当作多重集合:base 的每个元素消耗一个深度相等的匹配。

每个父节点下只出现一次的元素按名字匹配,所以这个选项只在同名标签重复出现时才有影响。

场景 1:中间插入一个元素

一条日志在第二个位置新增了一条事件:

<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]

场景 2:纯乱序

同样的步骤,不同的顺序:

<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 的沉默才是对的。选错方法,要么被噪声淹没,要么漏掉真正的回归。

场景 3:元素被原地修改

这是文档没有明说的取舍。重复块里一个子元素变了:

<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 是唯一能保住字段级精度的方法。

场景 4:重复元素是被计数的,不是被忽略的

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 IndexO(n) 次比较每对再各加一次递归比较
LCSO(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 文档。