文本对比与差异补丁实战:Diff 算法原理与在线工具高效使用

文本文本处理工具

Diff:无处不在的差异计算

从 Git 版本管理到代码审查,从配置变更到数据同步——Diff 是软件开发的基础操作。

场景 Diff 类型 工具
代码变更 行级 Diff Git
配置对比 键值 Diff 在线对比工具
JSON 数据 结构化 Diff json-diff
法律文档 语义 Diff 专用工具
翻译校对 字符级 Diff 在线对比

一、Myers Diff 算法原理

核心思想

Myers 算法寻找将源文本转换为目标文本的最短编辑脚本(SES)——最少插入/删除操作。

源: ABCABBA
目标: CBABAC

编辑脚本:
- A    (删除 A)
  B    (保留 B)
- C    (删除 C)
  C    (保留 C → 插入 C 在前面)
  A    (保留 A)
  B    (保留 B)
- B    (删除 B)
  A    (保留 A)
+ C    (插入 C)

算法图示

      0   1   2   3   4   5   6   7
  ┌───┬───┬───┬───┬───┬───┬───┬───┐
0 │ → │ ↘ │   │   │   │   │   │   │  A
  ├───┼───┼───┼───┼───┼───┼───┼───┤
1 │ ↓ │   │ ↘ │   │ → │ ↘ │   │   │  B
  ├───┼───┼───┼───┼───┼───┼───┼───┤
2 │   │ ↓ │   │ ↘ │ ↓ │   │ ↘ │   │  C
  ├───┼───┼───┼───┼───┼───┼───┼───┤
3 │   │   │ ↓ │   │   │ ↓ │   │ ↘ │  A
  ├───┼───┼───┼───┼───┼───┼───┼───┤
4 │   │   │   │ ↓ │ ↘ │   │ ↓ │   │  B
  ├───┼───┼───┼───┼───┼───┼───┼───┤
5 │   │   │   │   │ ↓ │   │   │ ↓ │  B
  ├───┼───┼───┼───┼───┼───┼───┼───┤
6 │   │   │   │   │   │ ↓ │ ↘ │   │  A
  └───┴───┴───┴───┴───┴───┴───┴───┘
    C   B   A   B   A   C   B   A

→ = 右移(删除)  ↓ = 下移(插入)  ↘ = 对角(保留)

时间复杂度

情况 复杂度 说明
最优 O(ND) N=文本长度,D=差异数
最差 O((N+M)D) 完全不同的文本
典型 O(N+D²) 少量差异

二、使用工具库文本对比

操作步骤

  1. 打开 文本对比工具
  2. 在左侧粘贴原始文本
  3. 在右侧粘贴修改后文本
  4. 查看差异结果:
    • 红色:删除的行
    • 绿色:新增的行
    • 黄色:修改的行
  5. 支持字符级差异高亮,精确到每个字符变化

对比模式

模式 粒度 适用场景
行级 按行对比 代码变更
字级 按词对比 文档修订
字符级 按字符对比 精确校对

三、JSON Diff 特殊技巧

使用 JSON Diff 工具

  1. 打开 JSON Diff 工具
  2. 粘贴两份 JSON 数据
  3. 查看结构化差异:
    • 新增/删除/修改的键
    • 值变化(含类型变化)
    • 嵌套路径

JSON Diff vs 文本 Diff

// 原始
{ "name": "张三", "age": 25, "tags": ["a", "b"] }

// 修改
{ "name": "张三", "age": 26, "tags": ["a", "c"], "email": "z@test.com" }
对比方式 结果
文本 Diff 按行显示差异,难以理解
JSON Diff age: 25→26, tags[1]: "b"→"c", +email: "z@test.com"

四、统一 Diff 格式

格式规范

--- a/config.json
+++ b/config.json
@@ -1,5 +1,6 @@
 {
   "name": "my-project",
-  "version": "1.0.0",
+  "version": "2.0.0",
   "description": "A tool",
-  "private": true
+  "private": true,
+  "license": "MIT"
 }
符号 含义
--- 原始文件
+++ 修改文件
@@ -a,b +c,d @@ 差异位置(原始第a行起b行,修改第c行起d行)
- 删除的行
+ 新增的行
(空格) 上下文行

五、补丁生成与应用

生成补丁

import * as Diff from 'diff';

const oldText = 'Hello World\nGoodbye World';
const newText = 'Hello World\nGoodbye Cruel World';

// 生成补丁
const patch = Diff.createPatch('file.txt', oldText, newText, 'old', 'new');
console.log(patch);

输出:

--- old/file.txt
+++ new/file.txt
@@ -1,2 +1,2 @@
 Hello World
-Goodbye World
+Goodbye Cruel World

应用补丁

// 正向应用
const result = Diff.applyPatch(oldText, patch);

// 反向应用(回滚)
const reversed = Diff.applyPatch(newText, Diff.reversePatch(patch));

三向合并

const base = '原始文本';
const branchA = 'A 的修改';
const branchB = 'B 的修改';

const merged = Diff.mergeDiff3(base, branchA, branchB);
// 自动合并,冲突时标记 <<<<<<< / ======= / >>>>>>>

六、实际应用场景

配置文件变更检测

const oldConfig = JSON.parse(fs.readFileSync('config.old.json', 'utf-8'));
const newConfig = JSON.parse(fs.readFileSync('config.new.json', 'utf-8'));

const diff = Diff.diffJson(oldConfig, newConfig);
diff.forEach(part => {
  if (part.added) console.log('新增:', JSON.stringify(part.value, null, 2));
  if (part.removed) console.log('删除:', JSON.stringify(part.value, null, 2));
});

翻译文件校对

// 对比中英文翻译键的一致性
const zhKeys = Object.keys(flatten(zhTranslation));
const enKeys = Object.keys(flatten(enTranslation));

const missingInEn = zhKeys.filter(k => !enKeys.includes(k));
const missingInZh = enKeys.filter(k => !zhKeys.includes(k));

API 响应回归测试

const expected = await fs.readFile('fixtures/expected.json', 'utf-8');
const actual = await fetch('/api/data').then(r => r.json());

const diff = Diff.diffJson(JSON.parse(expected), actual);
if (diff.some(d => d.added || d.removed)) {
  throw new Error('API 响应与预期不符');
}

七、性能优化

优化策略 效果 适用
限制上下文行数 减少输出体积 大文件
哈希预处理 跳过相同行 超大文件
分块对比 并行处理 超长文本
行级先于字符级 减少计算量 代码文件
// 限制上下文行数
const patch = Diff.createPatch('file', old, new, '', '', { context: 3 });
#Diff#文本对比#Patch#Myers#代码审查