如何比较两段文字的差异?Diff 算法原理与文字比对工具完整指南

每次保存文档、提交代码,或是把一份合同送去审阅之后,都会产生一个问题:「这个版本和上一个版本,到底哪里不一样?」文字比对(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 启用。它的核心思路是:

  1. 只在两个版本中都「唯一出现」的行上建立锚点
  2. 根据这些锚点将文件分割成区段
  3. 对各区段递归套用 LCS

实际效果是:Patience diff 在处理代码重构(大量函数移动)时,比 Myers diff 产生更直观易读的输出。

算法时间复杂度适用场景默认于
LCS(动态规划)O(mn)理论基础,小文件
Myers diffO(n + d²)一般代码比对Git、GNU diff
Patience diffO(n log n)代码重构、函数移动Bazaar(可选)
Histogram diffO(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 diffgit showgit 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. 如何使用在线文字比对工具

在线文字比对工具(如本站的文字比对工具)不需要安装任何软件,适合快速比对两段纯文字:

  1. 粘贴文字:将旧版本粘贴入左栏,新版本粘贴入右栏
  2. 选择比对模式:逐行或逐字符(视工具而定)
  3. 查看结果:删除的内容以红色标示,新增的内容以绿色标示,未变更的部分保持原色
  4. 逐差异导航:使用「上一处」「下一处」按钮快速跳至各差异位置

在线工具特别适合非技术人员进行文档比对,不需要熟悉命令行环境,操作直观。

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 等版本控制工具,也能在文档协作、合同审查等日常场景中更精准地追踪每一处修改。