文字對比與差異補丁實戰: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"

四、Unified 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#代码审查