Google diff-match-patch源代码解析:听说比GNU diff-patch更厉害?(一)

本文探讨Google的diff-match-patch算法,指出其相对于GNU diff的优势在于字符级比较和灵活的定制化操作。文章介绍了算法的核心——如何通过INSERT, DELETE, EQUAL操作定义字符串转换,并详细讲解了Diff的计算过程。此外,还预告了后续将分析算法的语义优化和富文本支持。" 121513713,11512937,中国触觉传感器市场分析与投资前景研究报告,"['传感器技术', '市场分析', '投资', '智能硬件', '物联网', '触觉传感']
摘要由CSDN通过智能技术生成

前言

GUN diff 与 Google diff-match-patch的区别

最近研究了google工程师开发的计算文本之间差异值的diff算法。该算法可以计算出不同文本之间的差异值并且生成patch文件来体现文本之间的差异。
GNU diff对于diff的计算是基于行的,也就是说如果使用GUN diff来计算两段文本之间的差异,两段文本的某一行绝大多数内容都相同,但是行内的某些字符有轻微的不同之处,那么GUN diff就会直接将其判定为冲突。比如:

第一行:
Google diff-match-patch源代码解析

第二行:
Google diff-patch源代码解析

那么使用GUN diff计算的结果就是:
< Google diff-match-patch源代码解析
---
> Google diff-patch源代码解析

而且使用GUN diff,如果两个文本同时修改了同一行,那么很容易引起冲突;冲突之后会将冲突结果存放入一个文件之中。如果使用基于GUN diff的merge方法,那么会将冲突结果直接写入合并后的文本之中。形如:

<<<<<<< 1.txt
Google diff-match-patch源代码解析=======
Google diff-patch源代码解析>>>>>>> 2.txt

而Google diff-match-patch的优势之处在于,它没有GUN diff这么死板。 Google diff-match-patch的算法是基于字符的,也就是说尽管一行之内有不同,但是Google的算法还是能找到行内的不同之处和相同之处。
还是上面那个例子,如果使用Google diff算法就会出现下面的结果:
在这里插入图片描述
最奇妙的是,Google diff算法还可以根据用户不同的需要进行定制化的diff操作,包括但不限于:

  • 语义化优先的diff计算
  • 效率优先的diff计算(可以设置diff单元的最小粒度)
  • 设置ddl时间的diff计算(在n秒内完成diff计算操作)

如果想体验google diff-match-patch算法,这里提供了一个基于JS实现的在线demo
另外该算法是开源的,具有多种语言的实现版本(包括但不限于:Java、JS、Python),git仓库在这里。

注释

这篇文章准备分几个部分来写,根据目前的构思,想分为三个部分完成这一篇博文:

  1. diff_match_patch的diff算法是如何实现的?
  2. diff_match_patch如何对生成的diff进行语义上的优化?
  3. 如何进一步优化diff_match_patch,以支持对富文本(标签文本)的diff计算?

这是第一篇博文,分析diff_match_patch的diff算法。

diff算法实现——如何定义一系列操作,来将字符串A转换为字符串B?

操作的定义

或许有人记得动态规划算法的教学题——编辑距离。如果想把字符串A转换为字符串B,那么最少需要多少步的操作?
很遗憾这里使用的似乎并不是动态规划算法;但是这两个问题之间有一个很重要的共同点:对于操作的定义。字符串的修改,无非“插入字符”、“删除字符”、“修改字符”。而为了简化操作的种类,“修改字符”可以用“删除字符”和“修改字符”的组合来完成。比如A字符串为aaa,B字符串为aab,从A到B的转换可以是:

  • 将A字符串的最后一位替换b
  • 将A字符串的最后一位删除;在A字符串的最后一位添加字符`b``

那么Google diff-match-patch算法中是怎么定义操作的呢?

  /**
   * 将操作分为三种枚举类型:Delete,Insert,Equal
   */
  public enum Operation {
   
    DELETE, INSERT, EQUAL
  }

  public static class Diff {
   
    /**
     * INSERT, DELETE , EQUAL.这三种枚举操作类型中的一种
     */
    public Operation operation;
    /**
     * 操作的文本内容
     */
    public String text;

	... ...
}

这里定义的三种操作:INSERT, DELETE, EQUAL对应的三种Diff情形,举个例子:
比如,文本AHelloworld 到文本BGoodbyeworld的转换就可以定义为三个Diff:

  1. Diff(Operation.DELETE, “Hello”): delete “Hello” //删除Hello文本
  2. Diff(Operation.INSERT, “Goodbye”): add “Goodbye” //增添Goodbye文本
  3. Diff(Operation.EQUAL, " world."): keep " world." //保持原有的world不变

定义完了操作,基于这个操作集,任意(String A,String B)的转换经过计算,都可以转换为一系列Diff的组合——LinkedList<Diff>:哪里删除了,哪里增加了,哪里没有变。

Diff的计算

其实整个Google diff-match-patch的源代码带上注释有大约2500行(Java实现版本)。但是真正“单纯”的计算Diff的代码只有不到300行。剩下的很多代码其实都是用于后续Diff类转化为文本服务的——为了应对客户对于导出Diff定制化的需求:有的想快速把Diff导出,有的想基于语义导出Diff等等。

这里的导出Diff指的是,将计算出LinkedList<Diff>的重新生成为用户易于理解的Diff文本。

首先,整个进行Diff计算的入口(一上来就利用多态进行两次跳转,如果不进行特殊的参数配置则默认使用下面代码框中的第三段代码):

  public LinkedList<Diff> diff_main(String text1, String text2) {
   
    return diff_main(text1, text2, true); // 这里的true暂时可以忽略(跟本篇的关系不大),绝大多数情况下可以直接置为True。
  }


  public LinkedList<Diff> diff_main(String text1, String text2, boolean checklines) {
   
    ......
    return diff_main(text1, text2, checklines, deadline);
  }

  /**
   * 计算两个文本之间的Diff。 通过给相似的文本“掐
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值