如何比較兩段文字的差異?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 Big5),需先統一編碼再比對。
  • 演算法選擇:不同演算法對同一差異的「切割方式」不同,切換演算法有時可得到更直覺的結果。

7.3 diff 與 merge 有什麼關係?

Merge(合併)是 diff 的延伸應用:當兩個人分別基於同一份原始版本做了修改,merge 工具會先各別 diff,再嘗試將兩份差異合併。當兩份修改涉及同一行時,就會產生「合併衝突」(merge conflict),需要人工介入解決。

8. 小結

文字比對技術看似簡單,背後卻是經典的演算法問題。從 LCS 到 Myers diff,從逐行到逐字元,不同的比對策略適合不同的使用場景。掌握這些原理,不僅能更有效地使用 Git 等版本控制工具,也能在文件協作、合約審查等日常場景中更精準地追蹤每一處修改。