每次儲存文件、提交程式碼,或是把一份合約送去審閱之後,都會產生一個問題:「這個版本和上一個版本,到底哪裡不一樣?」文字比對(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 Big5),需先統一編碼再比對。
- 演算法選擇:不同演算法對同一差異的「切割方式」不同,切換演算法有時可得到更直覺的結果。
7.3 diff 與 merge 有什麼關係?
Merge(合併)是 diff 的延伸應用:當兩個人分別基於同一份原始版本做了修改,merge 工具會先各別 diff,再嘗試將兩份差異合併。當兩份修改涉及同一行時,就會產生「合併衝突」(merge conflict),需要人工介入解決。
8. 小結
文字比對技術看似簡單,背後卻是經典的演算法問題。從 LCS 到 Myers diff,從逐行到逐字元,不同的比對策略適合不同的使用場景。掌握這些原理,不僅能更有效地使用 Git 等版本控制工具,也能在文件協作、合約審查等日常場景中更精準地追蹤每一處修改。