每次保存文档、提交代码,或是把一份合同送去审阅之后,都会产生一个问题:「这个版本和上一个版本,到底哪里不一样?」文字比对(diff)就是回答这个问题的技术核心。从 Unix 世界的 diff 命令,到 Git 的提交记录,到在线协作工具的修订模式,背后都是同一套逻辑。本文从算法原理出发,带你全面了解文字比对的运作方式。
1. 什么是 Diff?
Diff(difference)是一种计算两段文字之间最小差异集合的技术。给定「旧版本」(A)与「新版本」(B),diff 算法会找出:
- 哪些行(或字符)在 B 中是新增的
- 哪些行(或字符)在 B 中已被删除
- 哪些行(或字符)保持不变
这个差异集合越小越好——这就是「最小编辑距离」(Minimum Edit Distance)问题的核心,也是大多数 diff 算法的优化目标。
「Diff」同时指这项技术本身,以及其输出结果(一份描述差异的报告)。动词用法:「把这两个文件 diff 一下」,名词用法:「这个 diff 有三处修改」。
2. 核心算法:LCS 与 Myers Diff
2.1 最长公共子序列(LCS)
大多数 diff 算法的理论基础是最长公共子序列(Longest Common Subsequence,LCS)。LCS 是指在两个序列中都出现、且保持相对顺序的最长子序列。
例如:
- 序列 A:
猫、狗、鱼、鸟 - 序列 B:
猫、鱼、兔、鸟 - LCS:
猫、鱼、鸟(长度 3)
找到 LCS 之后,不在 LCS 中的 A 元素就是「删除」,不在 LCS 中的 B 元素就是「新增」。LCS 算法的时间复杂度为 O(mn),其中 m、n 分别是两段文字的长度。
2.2 Myers Diff 算法
1986 年,Eugene Myers 发表了一个比纯 LCS 更高效的 diff 算法,称为 Myers diff。它的特点是:
- 在最优情况下时间复杂度为 O(n + d²),其中 d 是差异数量
- 当差异很少时(这在实际使用中最常见),速度远快于 O(mn)
- 产生的差异倾向于「尽量保留原有结构」,可读性较好
Myers diff 目前是 Git、GNU diff 等主流工具的默认算法。
2.3 Patience Diff
Patience diff 是另一种常用算法,由 Bram Cohen(BitTorrent 作者)设计,Git 支持以 --diff-algorithm=patience 启用。它的核心思路是:
- 只在两个版本中都「唯一出现」的行上建立锚点
- 根据这些锚点将文件分割成区段
- 对各区段递归套用 LCS
实际效果是:Patience diff 在处理代码重构(大量函数移动)时,比 Myers diff 产生更直观易读的输出。
| 算法 | 时间复杂度 | 适用场景 | 默认于 |
|---|---|---|---|
| LCS(动态规划) | O(mn) | 理论基础,小文件 | — |
| Myers diff | O(n + d²) | 一般代码比对 | Git、GNU diff |
| Patience diff | O(n log n) | 代码重构、函数移动 | Bazaar(可选) |
| Histogram diff | O(n log n) | Myers 的改进版 | Git(可选) |
3. Unified Diff 格式
无论使用哪种算法,差异结果通常以「Unified diff」格式输出,这是业界标准格式,也是 git diff 默认使用的格式。
一个典型的 Unified diff 输出如下:
--- a/hello.txt
+++ b/hello.txt
@@ -1,5 +1,5 @@
第一行不变
-旧的第二行
+新的第二行
第三行不变
-被删除的第四行
第五行不变
+新增的第六行
格式说明:
---:旧版本(a)+++:新版本(b)@@:区块标头,-1,5表示旧版从第 1 行起共 5 行,+1,5表示新版从第 1 行起共 5 行- 空格开头:未变更的行(上下文行,默认显示 3 行)
-开头:被删除的行+开头:新增的行
4. 逐字符比对 vs 逐行比对
标准 diff 以「行」为单位比对,但有些工具提供更细致的比对模式:
| 模式 | 比对单位 | 适用场景 |
|---|---|---|
| 逐行比对 | 整行 | 代码、配置文件、一般文档 |
| 逐字比对(word diff) | 单词 | 自然语言文档、Markdown |
| 逐字符比对(char diff) | 单一字符 | 细微拼写修正、法律合同 |
Git 支持以 git diff --word-diff 切换到逐字比对模式;在线文字比对工具通常会同时标示行级与字符级的差异,让使用者一眼看出改动细节。
5. 实务应用场景
5.1 版本控制(Git)
Git 的每一次 commit 都内建 diff 记录。git diff、git show、git log -p 都是查看差异的常用命令。Pull request 的审查界面(GitHub、GitLab)本质上就是把 diff 以可视化方式呈现。
5.2 文档校订(Track Changes)
Microsoft Word 的「修订」、Google Docs 的「建议模式」,其底层都是 diff 的概念——记录谁在什么时候修改了哪些文字,让审阅者可以逐项接受或拒绝。
5.3 合同与法律文件
合同修订时,律师需要确认每一个字的改动。使用逐字符比对模式,可以清楚标出「originally」改成「previously」这样的细微措辞调整,避免遗漏重要改动。
5.4 配置文件与基础架构代码(IaC)
Terraform、Ansible 等 IaC 工具会在应用前显示「plan diff」,让工程师确认哪些云端资源将被新增、修改或删除,避免意外操作。
5.5 数据比对
CSV、JSON 导出的数据集在不同时间点之间的比对,可以用 diff 快速找出哪些字段被修改、哪些记录被新增或删除,适合数据审计与问题排查。
6. 如何使用在线文字比对工具
在线文字比对工具(如本站的文字比对工具)不需要安装任何软件,适合快速比对两段纯文字:
- 粘贴文字:将旧版本粘贴入左栏,新版本粘贴入右栏
- 选择比对模式:逐行或逐字符(视工具而定)
- 查看结果:删除的内容以红色标示,新增的内容以绿色标示,未变更的部分保持原色
- 逐差异导航:使用「上一处」「下一处」按钮快速跳至各差异位置
在线工具特别适合非技术人员进行文档比对,不需要熟悉命令行环境,操作直观。
7. 常见问题
7.1 diff 能比对图片或 PDF 吗?
标准 diff 是纯文字工具,无法直接比对二进制文件(如图片、PDF)。PDF 需先转换成纯文字才能比对文字内容。图片比对需要专用的影像差异工具(image diff),比较像素差异,不同于文字 diff 的概念。
7.2 为什么 diff 结果有时不如预期?
最常见的原因:
- 空白字符差异:缩进方式(Tab vs 空格)或行尾符号(Windows CRLF vs Unix LF)不同,会造成大量假差异。可使用
-w(忽略所有空白)或-b(忽略行尾空白)选项过滤。 - 编码差异:若两个文件使用不同字符编码(如 UTF-8 vs GBK),需先统一编码再比对。
- 算法选择:不同算法对同一差异的「切割方式」不同,切换算法有时可得到更直观的结果。
7.3 diff 与 merge 有什么关系?
Merge(合并)是 diff 的延伸应用:当两个人分别基于同一份原始版本做了修改,merge 工具会先各别 diff,再尝试将两份差异合并。当两份修改涉及同一行时,就会产生「合并冲突」(merge conflict),需要人工介入解决。
8. 小结
文字比对技术看似简单,背后却是经典的算法问题。从 LCS 到 Myers diff,从逐行到逐字符,不同的比对策略适合不同的使用场景。掌握这些原理,不仅能更有效地使用 Git 等版本控制工具,也能在文档协作、合同审查等日常场景中更精准地追踪每一处修改。