文本对比(Diff)工具

粘贴原文和修改稿,即可按行、词或字符查看新增和删除了什么。

diff 在计算什么:最长公共子序列(LCS)

找出两段文本的差异,就是回答“把原文改成修改稿,最少要删掉哪些行、插入哪些行”。这和求两个列表的最长公共子序列(LCS,Longest Common Subsequence)是同一个问题:在保持顺序的前提下,两边都能取出的最长序列。 属于 LCS 的内容是“没有变化的部分”,其余的内容,只在原文中出现就是删除,只在修改稿中出现就是新增。 因此编辑次数(删除 + 新增)等于 原文长度 + 修改稿长度 − 2 × LCS 长度。

本工具采用经典的动态规划:先填好 LCS 长度表,再从表中回溯读出编辑操作。时间和内存都与两个长度的乘积成正比, 所以先去掉两段文本开头和结尾相同的部分,只对中间不同的区段建表;若乘积仍然过大,就给出提示而不是让页面卡死(上限见下文)。 Git 默认使用的 diff 基于 Eugene Myers 在 1986 年发表的另一种算法,两个版本越相似运行越快。两者都以最小编辑为目标, 但存在多个同样短的解时,选择的解可能不同,所以删除行和新增行的排布可能略有差别。

可以手工跟着算的例子

下面是算法讲解中的经典字符串 ABCABBA(原文)和 CBABAC(修改稿)。每个格子表示“从该位置到末尾的 LCS 长度”,左上角的格子就是整体的 LCS 长度。 这些数字是生成本页时用和工具相同的递推公式算出来的。

原文\修改稿CBABAC末尾
A4432210
B4432110
C4332110
A3332100
B3322100
B2222100
A1111100
末尾0000000

结果:LCS 长度为 4。原文 7 项、修改稿 6 项,所以最少编辑数为 7 + 6 − 2×4 = 5。 换成按行的例子更直观:原文为“苹果 / 香蕉 / 葡萄”,修改稿为“苹果 / 草莓 / 葡萄 / 橙子”,LCS 是“苹果、葡萄”,长度 2, 编辑数为 3 + 4 − 2×2 = 3,即删除“香蕉”1 行,新增“草莓”和“橙子”2 行。把这几行直接放进上面的输入框,就能看到同样的结果。

按行、按词、按字符有什么区别

按行适合以行为意义单位的文本,如源代码、配置文件、清单和合同条款。速度快、结果简洁,但行内哪怕只改了一个词,整行也会显示为删除加新增。 为此,当一条删除行和一条新增行相邻时,工具会把这两行再按词比较一次,把真正改动的词着色标出。按词适合写成长长一行的报告、文章初稿, 用来看哪个短语被改了。按字符适合核对错别字、数字差一位,以及账号、网址这类一个字符出错就出问题的内容;但文本很长时处理会明显变慢, 碰巧相同的字符也会让结果碎成难读的小片段,请只用于较短的片段。需要注意:本工具“按词”是以空白分隔的,中文句子里没有空格, 所以一段不含空格的中文会被当作一个词,整段只要有一个字不同就会整体显示为改动。要细看中文句子内部的差异,请用“按字符”。

单位适合的文本优点注意
行代码、配置、清单、条款快,结果短改一个词整行也会显示改动(行内高亮可弥补)
词英文长句、报告能看到改动的短语以空白分隔,不含空格的中文整句算一个词
字符数字、姓名、网址核对连一个字符的差别也能发现长文本慢,结果容易碎

空白和大小写选项什么时候用

内容相同,只是缩进变了,或行尾多了空格,导致每一行都显示为“不同”时,选择“忽略首尾空白”,只会留下真正的改动。 从文字处理软件复制来的文本常有连续两个空格,这时选“连续空白合并为一个”更合适。“忽略大小写”适用于只改了英文大小写的普通文章, 但在 userId 与 userID 是不同名字的代码对比中请关闭。这些选项只影响比较过程,屏幕上仍显示原文。 Windows(CRLF)与 Unix(LF)换行符不同会让每一行都看起来不一样,本工具在输入时就统一了换行,避免了这个问题。

如何阅读结果、用在哪里

绿色表示只在修改稿中出现(新增),红色表示只在原文中出现(删除)。比较合同或服务条款的修订前后,可以先按行通览, 再把改动过的行用按词方式复查。代码和配置文件建议按行并忽略空白,只看有意义的改动。要对比 JSON,可先用JSON 格式化把两边的缩进和排版统一,无谓的差异就会消失。改写完自荐信或论文后, 可以用字数统计查看篇幅变化。看起来相同却被判为不同时,请切换到按字符, 常见原因是全角空格、零宽字符这类看不见的字符,或同一个字符以不同编码形式保存。若发现规律后想批量替换名称, 可先在正则表达式测试器里试验表达式。

隐私、上限与不适用的情况

粘贴的内容全部在浏览器内处理,不会上传,也不会保存。实际的限制是内存:LCS 表每格存一个 32 位数字,去掉开头和结尾相同部分后, 如果需要比较的区段格子数超过 25,000,000,工具就不再建表而是显示提示。这大约相当于 100 MB,每一侧约 5,000 项。按行时是几千行, 按字符时则是几千个字符,所以长文档请改用按行或拆分后分别比较。

diff 不理解“移动”:把一段文字剪切后粘贴到别处,会显示为一处删除、另一处新增。工具只对比纯文本,不能发现 Word 或 PDF 中的格式、表格、图片的变化。 行内高亮是把相邻的删除行与新增行按顺序配对比较,如果行的顺序被大幅打乱,配对可能不准确。不支持三方合并和版本管理, 团队协作的代码请使用 Git 之类的版本控制工具。具有法律效力的文件,最终核对必须由人对照原件完成。

常见问题

一行里只改了一个词,为什么整行都显示红/绿?

这是按行对比的基本行为。删除行和新增行配对后,其中真正改动的词会用更深的颜色标出,请看颜色更深的部分。如果只想看短语,请把对比单位改成按词。

“忽略首尾空白”和“连续空白合并为一个”有什么区别?

前者只去掉每个比较项两端的空白;后者还会把项内部连续的空格或制表符当作一个空格,所以连按两次空格造成的差异也会被忽略。

内容看起来一样,为什么显示不同?

常见原因有看不见的空白(全角空格、零宽字符)、行尾空格、Unicode 规范化形式不同,以及大小写差异。可以试着切换选项,并用按字符方式找出具体不同的字符。

能检测出被移动的行吗?

不能。位置改变的行会显示为一处删除、另一处新增,这是基于 LCS 的 diff 的普遍特点。

结果会和 Git 的 diff 不一样吗?

可能略有不同。存在多种同样少的编辑方案时,不同算法选择的路径不同,即使编辑数相同,删除行和新增行的位置也可能稍有差别。Git 使用 Myers 系列算法,还可选择其他方式。

“复制差异”按什么格式复制?

按行时,未改动的行前加两个空格,删除行前加“- ”,新增行前加“+ ”。按词和按字符时,删除内容用 [-…-] 包起来,新增内容用 [+…+] 包起来。