Java实现字符串文本差异化比较

本文探讨了 Java Diff Utils 及其各种功能。Java Diff Utils 提供了一个灵活的开源解决方案,用于比较 Java 应用程序中的文本数据。从基本的逐行差异比较到完整的统一差异生成和修补功能,它是构建强大的版本控制或变更跟踪系统的基础工具。
Java Diff Utils 具有最少的设置和高度可读的输出,对于使用版本化数据、协作编辑工具或文件监控系统的开发人员来说,它是必备的。

在现代软件开发中,跟踪和可视化不同版本文件或内容之间的变化至关重要。无论我们构建的是版本控制系统、协作编辑器还是代码审查工具,高效地比较内容都至关重要。在 Java 中,实现此目的的一种流行方法是使用Java Diff Utils。

本教程演示了如何使用 Java Diff Utils 库执行各种任务,包括逐行比较文本内容、生成统一差异、应用补丁以恢复或修改内容以及构建并排差异视图。

了解 Java Diff 实用程序及其主要优点
Java Diff Utils 是一个轻量级且功能强大的库,用于计算文本数据之间的差异。它支持字符级和行级比较,并生成统一的差异输出。

它通常用于版本控制系统,并应用补丁将一个版本的数据转换为另一个版本。此实用程序提供了各种类和方法来简化比较过程。

以下是 Java Diff Utils 的主要优点:

  • 简单:提供简洁直观的 API 和静态实用方法
  • 可扩展性:轻松与 Spring Boot 服务和控制器集成
  • 跨平台:兼容任何支持 Java 的操作系统
  • 开源:根据 Apache 许可自由使用和修改

这些功能使 Java Diff Utils 成为 Java 应用程序中可靠文本比较功能的理想选择。

设置 Diff Utils
在深入研究之前,我们将通过Spring Initializr使用 Maven设置一个简单的Spring Boot应用程序。

虽然 Java Diff Utils 可以在纯 Java 中使用,只需手动管理 JAR 文件和类路径即可,但 Spring Boot 和 Maven 可以通过pom.xml自动处理所有依赖项,从而简化了这一过程。这种方法不仅减少了设置时间,还确保了跨环境的更好的可移植性和一致性。Maven 负责下载、管理和解析所有必需的构件。

首先,我们将Java Diff Utils 依赖项添加到我们的pom.xml文件中:

<dependency>
    <groupId>io.github.java-diff-utils</groupId>
    <artifactId>java-diff-utils</artifactId>
    <version>4.12</version>
</dependency>

通过此设置,Maven 可确保在编译和运行时均可使用正确版本的 Java Diff Utils。

现在,我们可以直接使用DiffUtils、Patch和UnifiedDiffUtils等核心类了 。这些类都是基于实用程序的,这意味着无需显式创建实例。这种设计允许直接集成到服务类、控制器层或独立的 Java 组件中。

在 Java 中使用 Diff 实用程序
在本节中,我们将逐步构建一些基本组件,以学习如何在各种场景中使用 Java Diff Utils 库。我们将探讨几个核心用例,并研究在 Java 应用程序中使用各种策略比较文本内容的实现。我们将使用 Java Diff Utils 库创建用于比较内容、生成 diff、应用补丁以及并排查看更改的实现。

让我们进一步探讨其核心用例和实现。

比较字符串列表
让我们创建一个名为TextComparatorUtil的工具类,用于比较两个字符串列表,并生成一个表示它们差异的补丁。这个工具类简化了识别文本版本之间差异的过程:

class TextComparatorUtil {
    public static Patch<String> compare(List<String> original, List<String> revised) {
        return DiffUtils.diff(original, revised);
    }
}

让我们验证TextComparatorUtil是否正确检测并报告两个字符串列表之间的变化:

@Test
void givenDifferentLines_whenCompared_thenDetectsChanges() {
    var original = List.of("A", "B", "C");
    var revised = List.of(
"A", "B", "D");
    var patch = TextComparatorUtil.compare(original, revised);
    assertEquals(1, patch.getDeltas().size());
    assertEquals(
"C", patch.getDeltas().get(0).getSource().getLines().get(0));
    assertEquals(
"D", patch.getDeltas().get(0).getTarget().getLines().get(0));
}

生成统一的差异
接下来,让我们创建一个类UnifiedDiffGeneratorUtil,它在两个字符串列表之间生成统一的差异,以标准补丁格式表示差异:

class UnifiedDiffGeneratorUtil {
    public static List<String> generate(List<String> original, List<String> revised, String fileName) {
        var patch = DiffUtils.diff(original, revised);
        return UnifiedDiffUtils.generateUnifiedDiff(fileName, fileName + ".new", original, patch, 3);
    }
}

当我们指定原始内容和修订内容以及文件名时,它会生成适合代码审查或版本控制系统的统一差异输出。

让我们编写一个 Junit 测试来确保UnifiedDiffGeneratorUtil在统一差异输出中正确突出显示修改过的行:

@Test
void givenModifiedText_whenUnifiedDiffGenerated_thenContainsExpectedChanges() {
    var original = List.of("x", "y", "z");
    var revised = List.of(
"x", "y-modified", "z");
    var diff = UnifiedDiffGeneratorUtil.generate(original, revised,
"test.txt");
    assertTrue(diff.stream().anyMatch(line -> line.contains(
"-y")));
    assertTrue(diff.stream().anyMatch(line -> line.contains(
"+y-modified")));
}

 应用补丁
接下来,我们编写一个PatchUtil类,生成并应用补丁来更新原始内容。它将原始字符串列表转换为修订版本:

class PatchUtil {
    public static List<String> apply(List<String> original, List<String> revised) throws PatchFailedException {
        var patch = DiffUtils.diff(original, revised);
        return DiffUtils.patch(original, patch);
    }
}

它首先计算差异,然后应用生成的补丁来更新原始内容。

现在,我们将验证PatchUtil是否正确应用了补丁,以便原始列表与修改后的列表相匹配:

@Test
void givenPatch_whenApplied_thenMatchesRevised() throws PatchFailedException {
    var original = List.of("alpha", "beta", "gamma");
    var revised = List.of(
"alpha", "beta-updated", "gamma");
    var result = PatchUtil.apply(original, revised);
    assertEquals(revised, result);
}

构建并排差异视图
最后,让我们创建一个 SideBySideViewUtil类,它提供一种方法,以可读的格式显示两个字符串列表之间的差异:

public class SideBySideViewUtil {
    private static final Logger logger = Logger.getLogger(SideBySideViewUtil.class.getName());
    public static void display(List<String> original, List<String> revised)
    {
        var patch = DiffUtils.diff(original, revised);
        patch.getDeltas().forEach(delta -> {
            logger.log(Level.INFO,"Change: " + delta.getType());
            logger.log(Level.INFO,
"Original: " + delta.getSource().getLines());
            logger.log(Level.INFO,
"Revised: " + delta.getTarget().getLines());
        });
    }
}

它识别每个更改,并打印更改类型以及原始版本和修订版本中相应的行。这使得更容易快速地可视化和理解文本版本之间的修改。

虽然 Java Diff Utils 不会直接生成 HTML 视图,但它会公开来自差异的源代码和目标行。这些可用于构建自定义的可视化表示,然后将其转换为适用于基于 Web 的差异查看器的格式化 HTML。

此测试验证在比较两个不同的字符串列表时,SideBySideViewUtil.display ()方法是否正确执行:

@Test
void givenDifferentLists_whenDisplayCalled_thenNoExceptionThrown() {
    List<String> original = List.of("line1", "line2", "line3");
    List<String> revised = List.of(
"line1", "line2-modified", "line3", "line4");
    SideBySideViewUtil.display(original, revised);
}