在Java中生成格式化的差异输出

21

有没有Java相关的库可以接受两个字符串,并返回格式化输出的字符串,就像*nix diff命令一样?

例如,输入:

test 1,2,3,4
test 5,6,7,8
test 9,10,11,12
test 13,14,15,16
test 1,2,3,4
test 5,6,7,8
test 9,10,11,12,13
test 13,14,15,16

作为输入,它会给你

test 1,2,3,4                                                    test 1,2,3,4
test 5,6,7,8                                                    test 5,6,7,8
test 9,10,11,12                                               | test 9,10,11,12,13
test 13,14,15,16                                                test 13,14,15,16

与直接将文件传递给diff -y expected actual一样。

我发现了这个问题,它提供了一些关于通用库的建议,可以为您提供编程输出,但我想要纯字符串结果。

我可以直接调用diff作为系统调用,但这个应用程序将在unix和windows上运行,我不能确定环境是否实际上有可用的diff

5个回答

15

java-diff-utils

DiffUtils是一个Java库,用于计算差异、应用补丁、生成并排视图等操作。

Diff Utils库是一个开源库,用于执行文本之间的比较操作:计算差异、应用补丁、生成统一的差异或解析它们、为易于未来显示(如并排视图)生成差异输出等。

构建这个库的主要原因是缺乏易于使用的库,这些库具有在处理差异文件时需要的所有常规功能。最初它受到了JRCS库和它很好的差异模块设计的启发。

主要特性

  • 计算两个文本之间的差异。
  • 能够处理不仅限于纯ASCII的内容。使用此库可以对任何实现hashCode()和equals()方法的类型的数组或列表进行差异处理。
  • 使用给定的补丁打补丁和还原文本。
  • 解析统一的差异格式。
  • 生成人类可读的差异。

活跃维护的分支似乎是 https://github.com/bkromhout/java-diff-utils。 - koppor
1
生成易于阅读的差异。这个做好了吗?你能给我一个例子吗? - Prakash Palnati

6
我最终自己编写了代码。不确定它是否是最佳实现,而且很丑,但它可以通过测试输入。 它使用java-diff来执行繁重的差异比较(使用任何apache commons StrBuilder和StringUtils代替标准Java StringBuilder)。
public static String diffSideBySide(String fromStr, String toStr){
    // this is equivalent of running unix diff -y command
    // not pretty, but it works. Feel free to refactor against unit test.
    String[] fromLines = fromStr.split("\n");
    String[] toLines = toStr.split("\n");
    List<Difference> diffs = (new Diff(fromLines, toLines)).diff();

    int padding = 3;
    int maxStrWidth = Math.max(maxLength(fromLines), maxLength(toLines)) + padding;

    StrBuilder diffOut = new StrBuilder();
    diffOut.setNewLineText("\n");
    int fromLineNum = 0;
    int toLineNum = 0;
    for(Difference diff : diffs) {
        int delStart = diff.getDeletedStart();
        int delEnd = diff.getDeletedEnd();
        int addStart = diff.getAddedStart();
        int addEnd = diff.getAddedEnd();

        boolean isAdd = (delEnd == Difference.NONE && addEnd != Difference.NONE);
        boolean isDel = (addEnd == Difference.NONE && delEnd != Difference.NONE);
        boolean isMod = (delEnd != Difference.NONE && addEnd != Difference.NONE);

        //write out unchanged lines between diffs
        while(true) {
            String left = "";
            String right = "";
            if (fromLineNum < (delStart)){
                left = fromLines[fromLineNum];
                fromLineNum++;
            }
            if (toLineNum < (addStart)) {
                right = toLines[toLineNum];
                toLineNum++;
            }
            diffOut.append(StringUtils.rightPad(left, maxStrWidth));
            diffOut.append("  "); // no operator to display
            diffOut.appendln(right);

            if( (fromLineNum == (delStart)) && (toLineNum == (addStart))) {
                break;
            }
        }

        if (isDel) {
            //write out a deletion
            for(int i=delStart; i <= delEnd; i++) {
                diffOut.append(StringUtils.rightPad(fromLines[i], maxStrWidth));
                diffOut.appendln("<");
            }
            fromLineNum = delEnd + 1;
        } else if (isAdd) {
            //write out an addition
            for(int i=addStart; i <= addEnd; i++) {
                diffOut.append(StringUtils.rightPad("", maxStrWidth));
                diffOut.append("> ");
                diffOut.appendln(toLines[i]);
            }
            toLineNum = addEnd + 1; 
        } else if (isMod) {
            // write out a modification
            while(true){
                String left = "";
                String right = "";
                if (fromLineNum <= (delEnd)){
                    left = fromLines[fromLineNum];
                    fromLineNum++;
                }
                if (toLineNum <= (addEnd)) {
                    right = toLines[toLineNum];
                    toLineNum++;
                }
                diffOut.append(StringUtils.rightPad(left, maxStrWidth));
                diffOut.append("| ");
                diffOut.appendln(right);

                if( (fromLineNum > (delEnd)) && (toLineNum > (addEnd))) {
                    break;
                }
            }
        }

    }

    //we've finished displaying the diffs, now we just need to run out all the remaining unchanged lines
    while(true) {
        String left = "";
        String right = "";
        if (fromLineNum < (fromLines.length)){
            left = fromLines[fromLineNum];
            fromLineNum++;
        }
        if (toLineNum < (toLines.length)) {
            right = toLines[toLineNum];
            toLineNum++;
        }
        diffOut.append(StringUtils.rightPad(left, maxStrWidth));
        diffOut.append("  "); // no operator to display
        diffOut.appendln(right);

        if( (fromLineNum == (fromLines.length)) && (toLineNum == (toLines.length))) {
            break;
        }
    }

    return diffOut.toString();
}

private static int maxLength(String[] fromLines) {
    int maxLength = 0;

    for (int i = 0; i < fromLines.length; i++) {
        if (fromLines[i].length() > maxLength) {
            maxLength = fromLines[i].length();
        }
    }
    return maxLength;
}

你能发布maxLength()和值为NONE的内容吗?感谢您节省了我大量的开发时间。 - Bostone
你是否发现java-diff-utils库不够用?只是想知道(请原谅双关语)。 - Marcus Junius Brutus

0

Busybox有一个非常精简的diff实现,应该不难转换成Java,但您需要添加双列功能。


0

http://c2.com/cgi/wiki?DiffAlgorithm 我在谷歌上找到这个链接,它提供了一些很好的背景和相关链接。如果你对算法有更深入的了解需求,可以阅读一本关于动态规划或者专门探讨该算法的书籍。拥有算法知识总是有益的 :)


在Java中获得可工作的实现是容易的 - 它们已经存在了。真正困难的部分是将输出格式化为所需的格式。 - madlep

0
你可以使用 Apache Commons Text库来实现这个功能。该库提供了基于“Eugene W. Myers”的非常高效的算法的“差异”能力。
这为您提供了创建自己的访问者的能力,以便您可以按照您想要的方式处理差异,并可能将其输出到控制台或HTML等。这是一篇文章,通过使用Apache Commons Text库和简单的Java代码,演示了一个在HTML格式中输出并排差异的简单而好看的例子

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接