确定两个文件是否存储相同的内容


70

您将如何编写Java函数boolean sameContent(Path file1,Path file2)来确定两个给定路径是否指向存储相同内容的文件?当然,首先,我将检查文件大小是否相同。这是存储相同内容的必要条件。但是,我想听听您的方法。如果两个文件存储在同一硬盘上(就像我大多数情况一样),这可能不是在两个流之间跳转太多次的最佳方法。


3
大小可以不同,同样的内容也可以。取决于几个因素。如果您确实想比较内容,那么一个简单的检查就是对两个文件进行校验和比较。您可以在文件的字节数组上使用md5。也可以使用字节数组的比较。
Rene M.

Answers:


89

究竟FileUtils.contentEqualsApache Commons IO和api的方法在这里

尝试类似:

File file1 = new File("file1.txt");
File file2 = new File("file2.txt");
boolean isTwoEqual = FileUtils.contentEquals(file1, file2);

在实际进行比较之前,它将进行以下检查:

  • 两个文件的存在
  • 传递的两个文件都是文件类型,而不是目录。
  • 以字节为单位的长度不应相同。
  • 两者都是不同的文件,而不是相同的。
  • 然后比较内容。

1
为了增加价值,我发现FileUtils.contentEqualsIgnoreEOL可以为不太严格的断言提供方便。
CloudyTrees's

这是一种很棒的方法,但是如果我要逐行匹配文件的内容,然后从2个文件中分别打印匹配的名称和ID
whatthefish

我们可以将其用于图像或视频比较吗?
PJ2104 '19

请注意,尽管这在性能方面可能不是最佳解决方案。commons-io(至少v2.5以下)使用IOUtils,而IOUtils则进行(按字节读取)[ github.com/apache/commons-io/blob/commons-io-2.5/src/main/java/ …(不过,至少在缓冲流上)。如果相差很大,那么您的性能可能会受到影响。
斯凯尔

29

如果您不想使用任何外部库,则只需将文件读入字节数组并进行比较(在Java-7之前不起作用):

byte[] f1 = Files.readAllBytes(file1);
byte[] f2 = Files.readAllBytes(file2);

通过使用Arrays.equals

如果文件很大,则应按此处所述BufferedInputStream逐块使用和读取文件,而不是将整个文件读入数组。


2
我希望我的程序也能处理大文件。这可能会导致OutOfMemoryError-如果无法分配所需大小的数组,例如文件大于2GB。编辑:对不起,我刚刚看到您关于处理大文件的评论。
主理想域

2
真正。这就是为什么我包括一个指向SO页面的链接的原因,其中提到使用BufferedInputStream并逐块读取而不是整个文件。没有必要重复SO中已经存在的答案。
Chthonic Project 2014年

13

如果文件很小,则可以将它们都读入内存并比较字节数组。

如果文件不小,则可以一个接一个地计算其内容的哈希值(例如MD5或SHA-1),然后比较哈希值(但这仍然会产生很小的错误机会),或者可以比较它们的哈希值内容,但是您仍然必须交替阅读流。

这是一个例子:

boolean sameContent(Path file1, Path file2) throws IOException {
    final long size = Files.size(file1);
    if (size != Files.size(file2))
        return false;

    if (size < 4096)
        return Arrays.equals(Files.readAllBytes(file1), Files.readAllBytes(file2));

    try (InputStream is1 = Files.newInputStream(file1);
         InputStream is2 = Files.newInputStream(file2)) {
        // Compare byte-by-byte.
        // Note that this can be sped up drastically by reading large chunks
        // (e.g. 16 KBs) but care must be taken as InputStream.read(byte[])
        // does not neccessarily read a whole array!
        int data;
        while ((data = is1.read()) != -1)
            if (data != is2.read())
                return false;
    }

    return true;
}

您不能只包装输入流BufferedInputStream吗?然后,该方法将像您使用的方法一样高效,read(byte[])但是没有复杂性,对吗?
aioobe

@aioobe是的,我们可以。我使用逐字节比较的原因是因为read(byte[])不能保证该方法完全读取传递的字节数组(javadoc说“它最多读取bytes.length)。如果基础流的源是文件,则当前的实现将读取完整的数组,但对此不做任何保证。正确处理非完整数组读取的代码会更复杂,并且会引起我的代码片段试图展示的原理的注意。
icza

1
我理解这一点,但是我想说的是,通过使用您,即使没有这种复杂性,您也可以摆脱困境BufferedInputStream(同时仍然可以提高效率)。
aioobe

@aioobe是的,你是对的。其他读者的一些背景知识:的javadocBufferedInputStream.read(byte[] b, int off, int len)确实声明它试图读取完整的数组。并且尽管BufferedInputStream没有覆盖FilterInputStream.read(byte[] b)FilterInputStream.read(byte[] b)状态的javadoc声明实现调用了read(b, 0, b.length)该方法(如果是a,BufferedInputStream则将调用该BufferedInputStream.read(byte[] b, int off, int len)方法)。
icza

3
我仍然不确定我们是否在同一页面上。我在想,通过简单地更改Files.newInputStream(file1)new BufferedInputStream(Files.newInputStream(file1))您的is1.read()电话将对应于简单的数组访问(在大多数情况下),整个工作read(byte[] ...)将在幕后进行。因此,我建议您通过包装输入流BufferedInputStreams并在如何增加速度方面添加注释,以增加额外的复杂性,从而改善您的答案。
aioobe 2015年

12

从Java 12开始,存在方法Files.mismatch-1如果文件内容没有不匹配,则返回该方法。因此该函数如下所示:

private static boolean sameContent(Path file1, Path file2) throws IOException {
    return Files.mismatch(file1, file2) == -1;
}

是否将整个文件加载到内存中?我在Google上找不到任何参考资料
Akinn

2
据我所知-不,它按8kb大小的块读取两个文件。
Nolequen

6

应该可以帮助您解决问题:

package test;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;

public class CompareFileContents {

    public static void main(String[] args) throws IOException {

        File file1 = new File("test1.txt");
        File file2 = new File("test2.txt");
        File file3 = new File("test3.txt");

        boolean compare1and2 = FileUtils.contentEquals(file1, file2);
        boolean compare2and3 = FileUtils.contentEquals(file2, file3);
        boolean compare1and3 = FileUtils.contentEquals(file1, file3);

        System.out.println("Are test1.txt and test2.txt the same? " + compare1and2);
        System.out.println("Are test2.txt and test3.txt the same? " + compare2and3);
        System.out.println("Are test1.txt and test3.txt the same? " + compare1and3);
    }
}

3

如果用于单元测试,则AssertJ提供一个名为hasSameContentAs的方法。一个例子:

Assertions.assertThat(file1).hasSameContentAs(file2)

现在是hasSameTextualContentAs
仓鼠

0

我知道我要参加这个聚会还很晚,但是如果您想使用直接的Java API并且没有第三方依赖关系,则内存映射IO是一种非常简单的方法。只需几次调用即可打开文件,映射它们,然后进行比较ByteBuffer.equals(Object)以比较文件。

如果您希望特定文件很大,那么这可能会给您带来最佳性能,因为您将大部分IO开销转移到了OS以及JVM的其他高度优化的部分上(假设您使用的是不错的JVM)。

直接从 FileChannel JavaDoc

对于大多数操作系统,将文件映射到内存比通过常规读写方法读取或写入几十KB数据要昂贵。从性能的角度来看,通常仅需要将较大的文件映射到内存中。

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;


public class MemoryMappedCompare {

    public static boolean areFilesIdenticalMemoryMapped(final Path a, final Path b) throws IOException {
        try (final FileChannel fca = FileChannel.open(a, StandardOpenOption.READ);
             final FileChannel fcb = FileChannel.open(b, StandardOpenOption.READ)) {
            final MappedByteBuffer mbba = fca.map(FileChannel.MapMode.READ_ONLY, 0, fca.size());
            final MappedByteBuffer mbbb = fcb.map(FileChannel.MapMode.READ_ONLY, 0, fcb.size());
            return mbba.equals(mbbb);
        }
    }
}


-2
package test;  

      import org.junit.jupiter.api.Test;

      import java.io.IOException;
      import java.nio.file.FileSystems;
      import java.nio.file.Files;
      import java.nio.file.Path;

import static org.junit.Assert.assertEquals;

public class CSVResultDIfference {

   @Test
   public void csvDifference() throws IOException {
       Path file_F = FileSystems.getDefault().getPath("C:\\Projekts\\csvTestX", "yolo2.csv");
       long size_F = Files.size(file_F);
       Path file_I = FileSystems.getDefault().getPath("C:\\Projekts\\csvTestZ", "yolo2.csv");
       long size_I = Files.size(file_I);
       assertEquals(size_F, size_I);

   }
}

它对我有用:)


欢迎使用Stack Overflow!尽管此代码段可以解决问题,但并未说明原因或答案。请附上代码说明,因为这确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。
塞缪尔·菲利普

2
您正在比较文件大小,而不是内容大小
Stefan Reich
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.