文本对比工具
并排比较两个文本,高亮显示新增、删除与未变更的内容。
文本对比不是"找不同",而是把改动规模翻译成可度量指标
文本对比(text diff)常被当成"两版文档哪里不一样"的肉眼游戏,但从工程与信息检索角度看,它真正解决的是三个可度量问题:改动有多大、改动分布在哪、两版有多相似。本工具基于 LCS(最长公共子序列)做逐行比对,把"相同行"作为锚点,把"只存在于某一侧的行"标记为新增或删除,输出四组计数(相同、新增、删除、总变更)和一种相似度视角,而不是一句模糊的"不一样"。理解这一点,你才能把对比结果当评审清单、合并依据或质量门槛来用,而不是当作一个高亮特效。
算法家族对照:四种主流 diff 算法各有所长
不同 diff 工具背后是同一道题目的不同解法——如何在两段序列里高效找出"变与不变"。下面把四种常见算法放在一起对照,重点不是背复杂度,而是明白"为什么有的工具改一行很快、有的对重组过的代码更友好"。
| 算法 | 核心思路 | 最适用场景 | 最好复杂度 | 最坏复杂度 |
|---|---|---|---|---|
| LCS(最长公共子序列) | 用动态规划求两段的最长公共子序列作锚点 | 短文本、教学演示、并排比对 | O(m+n) | O(m×n) |
| Myers O(ND) | 在编辑图(edit graph)上找最短编辑路径 | 代码版本 diff、git diff 底层 | O(N) | O((N+D)×D) |
| Patience Diff | 用"唯一且递增"的锚点行做耐心匹配 | 代码被大幅重排、移动块多 | 约 O(n log n) | 约 O(n log n) |
| Histogram Diff | 按出现频次给行加权重再选锚点 | 大文件、重复行多的日志/数据 | 约 O(n log n) | 约 O(n log n) |
说明:m、n 是两段各自的行数;N 是两版总规模,D 是实际差异量。本工具采用 LCS,因为它实现直观、结果贴近"求同存异"的直觉,适合文本、配置、文档的整体对照;当你要处理被大量移动或重排过的代码时,Myers / Patience / Histogram 往往更合适。
粒度对照:按行 / 按词 / 按字符 / 按字
diff 的"粒度"决定它把什么当作一个不可再分的比较单元。粒度越粗(按行),结果越干净、性能越好,但会忽略行内改动;粒度越细(按字符、按字),能定位到具体改了哪个字,但噪声也更多。下表给出四种粒度的取舍。
| 粒度 | 比较单元 | 适合场景 | 代价 / 注意 |
|---|---|---|---|
| 按行 | 整行文本 | 代码、配置、文章段落整体对照 | 行内小改会被标成整行删除+新增 |
| 按词 | 以空格/标点切分的词 | 英文散文、技术文档的句内改动 | 中文没有天然空格分词,需先分词 |
| 按字符 | 单个字符 | 精确排查拼写、ID、符号差异 | 结果冗长,适合短串而非长文 |
| 按字 | 单个汉字 | 中文句内改字、增删字的精确定位 | 与"按词"差异极大,见下 |
注意:中文的"按字符"和"按词"差异极大。中文书写没有空格,若用字符级 diff,把"这是第一行。"改成"这是修改后的第二行。"需要 5 次编辑(6 个字 vs 10 个字,单看改动行 LCS 编辑距离为 5);而若先做中文分词、按"词"对比,共同前缀"这是"会被保留,真正变化的只是"第一行 → 修改后的第二行"这一小块,报告改动量显著更小。英文天然按空格分词,按词和按字符的差距没这么夸张(同一处改动英文约 12 次字符编辑)。所以处理中文时,选对粒度比选算法更影响阅读体验。长文建议先用 字数统计 确认两侧长度是否对得上,再决定是否分段。
相似度与差异率:三种可精算的指标
"两版有多像"不能只靠感觉,下面三个指标都能用脚本 100% 复现,本工具算例数字即来自同一套公式。
1) Levenshtein 距离(编辑距离):把一个字符串变成另一个所需的最少单字符插入、删除、替换次数,距离越小越相似。
2) Jaccard 相似度:两集合交集除以并集,取值 0–1;用于行集合时,J = 相同行数 ÷(相同行数 + 新增行数 + 删除行数)。
3) 编辑距离归一化(相似度):Sim = 1 − Lev ÷ max( len(A), len(B) ),结果以百分比表示,越接近 100% 越相似。
以页面默认输入为例(原始三行 vs 新文本四行):整段字符级 Levenshtein 距离为 14(中文)/ 40(英文),归一化相似度约 66.7%(中文)/ 66.9%(英文);按行集合的 Jaccard 相似度为 40.0%。注意:行数对不上(3 行 vs 4 行)会拉低 Jaccard,但归一化编辑距离只看字符总量,因此两者给的"像不像"结论会不同——评审时建议同时看。
| 指标 | 公式 | 默认输入(中文) | 默认输入(英文) | 解读 |
|---|---|---|---|---|
| Levenshtein 距离 | 最少插入/删除/替换次数 | 14 | 40 | 越小越相似 |
| Jaccard(行集合) | 相同行 ÷ 全部出现行 | 40.0% | 40.0% | 受行数不对等差影响 |
| 归一化编辑距离 | 1 − Lev ÷ max(len) | 66.7% | 66.9% | 只看字符总量 |
unified diff 与 context diff 格式逐字段说明
命令行 diff 通常输出两种经典格式,理解字段含义才能把本工具的并排结果"翻译"成可粘贴的 patch。Markdown 文档可先在 Markdown 预览 里整理后再对比。
| 字段 | unified diff(以 @@ / + / − 标记) | context diff(以 * / ! / 上下文行标记) |
|---|---|---|
| 文件头 | --- a/file / +++ b/file 标旧新文件 | *** file1 / --- file2 分两栏标头 |
| 块头 | @@ -l,s +l,s @@ 标旧/新起止行与长度 | 用 *************** 分隔上下两块上下文 |
| 未变行 | 无前缀(空格开头) | 无前缀或上下文行 |
| 新增行 | + 开头 | + 开头 |
| 删除行 | - 开头 | - 开头 |
| 上下文行数 | 默认 3 行(可 -U n 调) | 默认上下各 3 行 |
本工具的并排视图等价于"把 unified diff 的 + / − 行左右对齐":左侧放旧版(删除行落在左、空位补白),右侧放新版(新增行落在右)。它没有文件头,因为对比的是两段纯文本而非磁盘文件。
常见文本对的差异率基准(实测)
下面四组样本用本工具同一套逐行 LCS 逻辑跑出,差异率定义为"变更行数 ÷(相同行数 + 变更行数)× 100%"。它们给你一个"多像算像、多改算大改"的直观标尺。若要传输大段文本,可先用 压缩/解压 工具减小体积再分段比对。
| 样本类型 | 行数(旧→新) | 相同 | 新增 | 删除 | 总变更 | 差异率 |
|---|---|---|---|---|---|---|
| 同文档微改(改 1 行 + 增 1 行) | 12 → 13 | 11 | 2 | 1 | 3 | 21.4% |
| 局部改写(改 4 行,保 8 行) | 12 → 12 | 8 | 4 | 4 | 8 | 50.0% |
| 另起炉灶重写(几乎全变) | 12 → 12 | 0 | 12 | 12 | 24 | 100.0% |
| 中英混排段落(改 2 行) | 8 → 8 | 6 | 2 | 2 | 4 | 40.0% |
注意:差异率只看行的"同/异",不关心行内改了多少字;一句里改一个字和整行重写,在行级差异率里都算"1 行变更"。要区分行内改动幅度,需回到上一节的字符级 / 字级 Levenshtein。
三个真实算例(页面默认输入)
下面三个算例全部使用本工具文本框里的默认内容,数字与页面点击"对比"后看到的一致。
算例 1:默认输入的行级 diff 统计
原始文本框(3 行):
"你好,欢迎使用文本对比工具!"
"这是第一行。"
"第三行未改变。"
新文本框(4 行):
"你好,欢迎使用文本对比工具!"
"这是修改后的第二行。"
"第三行未改变。"
"第四行是新内容。"
LCS 对齐后:第 1 行与第 3 行相同(2 行),"这是第一行。"被删除(1 行),"这是修改后的第二行。"与"第四行是新内容。"为新增(2 行)。统计为:相同 2、新增 2、删除 1、总变更 3,差异率 = 3 ÷ (2+3) = 60.0%。并排视图里只有第 2 行附近与第 4 行被高亮,其余保持白色不变。
算例 2:中文按"字"粒度看改动行
聚焦被改动的这一行:旧"这是第一行。"(6 字)vs 新"这是修改后的第二行。"(10 字)。按字级 Levenshtein 需要 5 次编辑(插入"修改后"的 3 字、把"一"替换为"二"、在"。"前补"的")。若改为按词粒度(先中文分词),共同前缀"这是"被保留,真正变化只是"第一行 → 修改后的第二行",报告改动量明显更小。这正说明中文处理时"粒度"比"算法"更影响阅读噪声——本工具按行比较,行内差异可结合 字数统计 辅助判断。
算例 3:默认输入的整体相似度
把两段完整文本喂给相似度公式:字符级 Levenshtein 距离 = 14,归一化相似度 ≈ 66.7%;按行集合的 Jaccard = 40.0%。两个指标一高(66.7%)一低(40.0%)看似矛盾,其实一致——归一化编辑距离只看"字符改了多少",而 Jaccard 被"3 行变 4 行"的行数缺口拉低。结论是:这段文本字符层面保留了约三分之二,但行结构变了四成。
与 git diff / 版本管理的衔接
git 在底层用 Myers(及其变体)做 diff,并叠加 rename 检测、hunk 合并等策略。本工具与 git diff 的关系有三层:其一,本工具是纯文本、无文件概念的"迷你 diff",适合快速对比两段粘贴内容;其二,当你想把结果接回 git,应把差异以 unified diff 形式保存(参考上节字段),git apply 才能识别;其三,git 的"行号"基于文件全局行号,本工具只给相对位置,合并冲突(<<<<<<< / ======= / >>>>>>>)需回到 git 环境处理。需要统计改动规模时,可先用本工具做粗筛,再回到命令行跑 git diff --stat 看全仓汇总。更系统的对比思路见博客长文 文本对比实战指南。
补充问答
文本对比工具用的是哪种算法,为什么默认按行比较?
本工具使用 LCS(最长公共子序列)动态规划做逐行比对,把相同行作为锚点,只出现在某一侧的行标记为新增或删除。按行比较实现直观、性能稳定,适合文本、配置和文档的整体对照;处理被大量移动或重排的代码时,Myers、Patience、Histogram 等算法更合适。
中文文本用按字符还是按词对比,差异更大?
差异极大。中文没有空格分词,字符级 diff 把"这是第一行。"改成"这是修改后的第二行。"需 5 次编辑;若先做中文分词按词对比,共同前缀"这是"被保留,真正变化只是"第一行 → 修改后的第二行"。英文天然按空格分词,按词与按字符差距较小(同处改动约 12 次字符编辑)。
unified diff 和 context diff 有什么区别,该看哪种?
unified diff 用 @@ -l,s +l,s @@ 块头加 + / − 前缀,新旧改动并排在一栏,最常用;context diff 用 *** 与 --- 分两栏标头、用 ! 标改动行,上下文更分散。本工具的并排视图等价于把 unified diff 的 + / − 行左右对齐。
差异率(改动比例)是怎么算出来的?
本工具的差异率 = 变更行数 ÷(相同行数 + 变更行数)× 100%。变更行数 = 新增行数 + 删除行数。它只看行的同异,不关心行内改了多少字;要衡量行内幅度需用字符级 Levenshtein。
本工具的对比结果能直接用到 git 里吗?
本工具是纯文本迷你 diff,无文件概念。要接回 git,应把差异保存为 unified diff 格式(含 --- / +++ 文件头与 @@ 块头),git apply 才能识别;合并冲突标记需回到 git 环境处理。粗筛改动规模后,可用 git diff --stat 看全仓汇总。