Java文件 equals

24

我不知道你们是怎么想的,但至少在下面的代码中,我希望f1应该等于f2,但显然情况并非如此!你们对此有什么想法?看来我得写自己的equals方法来支持它,对吗?

import java.io.*;

public class FileEquals
{
    public static void main(String[] args)
    {
        File f1 = new File("./hello.txt");
        File f2 = new File("hello.txt");
        System.out.println("f1: " + f1.getName());
        System.out.println("f2: " + f2.getName());
        System.out.println("f1.equals(f2) returns " + f1.equals(f2));
        System.out.println("f1.compareTo(f2) returns " + f1.compareTo(f2));
    }
}

1
Java 7的Path类也是如此。但是存在像Path.normalize()或Files.isSameFile()这样的方法。 - Luciano
通过显示实际输出,您可以为此问题的所有查看者节省一些时间。我原以为“equals”和“compareTo”的结果是相互矛盾的。但事实并非如此,“equals”返回false,“compareTo”返回-58,表示按字典顺序“小于”。@Luciano:请注意,在这种情况下,“Files.isSameFile”将尝试打开文件,因为路径不相等,并且可能会出现“NoSuchFileException”错误。 - bluenote10
7个回答

34

不,这并不是事实。因为equals比较的是绝对路径的相等性(在你上面的例子中类似于:

)

some-project\.\hello.txt
some-project\hello.txt

因此它们自然不同。

看起来我必须编写自己的equals方法来支持它,是吗?

可能是的。但首先,你必须知道你想要比较什么?只有路径名吗?如果是,可以通过比较其规范路径来实现:

f1.getCanonicalPath().equals(f2.getCanonicalPath())

但如果你想比较两个不同文件的内容,那么是的,你应该编写自己的方法 - 或者只是从互联网上某个地方复制。


1
我实际上想做的是类似于“fileList.contains(file)”这样的操作,该方法会调用equals方法。 - aandeers
答案让我感到困惑。请查看JDK中UnixFileSystem.java的源代码:public int compare(File f1,File f2){return f1.getPath().compareTo(f2.getPath());} @G.Demecki 我不同意:equals正在比较绝对路径的相等性。 - linjiejun

9

要正确地测试equals,您必须调用getCanonicalFile()。例如:

public static void main(String[] args) throws IOException
   {
       File f1 = new File("./hello.txt").getCanonicalFile();
       File f2 = new File("hello.txt").getCanonicalFile();
       System.out.println("f1: " + f1.getAbsolutePath());
       System.out.println("f2: " + f2.getAbsolutePath());
       System.out.println("f1.equals(f2) returns " + f1.equals(f2));
       System.out.println("f1.compareTo(f2) returns " + f1.compareTo(f2));
   }

如果相等,则返回true。请注意,getCanonicalFile可能会抛出IOException,因此我将其添加到方法签名中。


5

如果您只想比较每个文件的内容,可以像这样将内容读入字节数组:

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

然后从那里精确比较您想要的内容。

请注意,此方法调用仅存在于Java 7中。对于旧版本,Guava和Apache有一些方法可以完成类似的操作,但名称和详细信息可能会有所不同。

编辑:或者更好的选择(特别是如果您正在比较大型文件),可能是按字节比较而不是将整个文件加载到内存中,像这样:

FileInputStream f1 = new FileInputStream(file1);
DataInputStream d1 = new DataInputStream(f1);
FileInputStream f2 = new FileInputStream(file2);
DataInputStream d2 = new DataInputStream(f2);

byte b1 = d1.readByte();
byte b2 = d2.readByte();

然后从那里开始比较。


1
我会首先比较文件的大小,如果有的话。 - Luciano
6
将文件进行比较是一个极为糟糕的想法。 - unbeli
1
@unbeli 请详细说明。我在许多单元测试中使用了类似的代码,其中一个文件包含正确的结果,另一个文件包含程序/算法生成的结果。这不是 OP 想要做的事情(因为他已经详细说明了),但 Brian 说了 CONTENTS,他甚至将其大写。 - user949300
@Brian Snow,想一想:如果这两个文件的第一个字节不同,读取整个文件有什么意义呢?如果文件很大会怎样?你真的需要将两个文件都加载到内存中吗? - unbeli
如果文件预计99%的时间是相等的,那么99%的时间你必须读取每个字节。 - user949300
显示剩余8条评论

2
我发现比较两个文件的更快方法如下。
这只是围绕它工作的建议。
不确定性能(如果文件每个都有10 GB呢?)
    File file = new File("/tmp/file.txt");
    File secondFile = new File("/tmp/secondFile.txt");

    // Bytes diff
    byte[] b1 = Files.readAllBytes(file.toPath());
    byte[] b2 = Files.readAllBytes(secondFile.toPath());

    boolean equals = Arrays.equals(b1, b2);

    System.out.println("the same? " + equals);

    // List Diff
    List<String> c1 = Files.readAllLines(file.toPath());
    List<String> c2 = Files.readAllLines(secondFile.toPath());

    boolean containsAll = c1.containsAll(c2);
    System.out.println("the same? " + containsAll);                
}

编辑

但是,在Unix系统上使用diff实用程序会更快速和详细。 这取决于您需要比较的内容。


1
如果您只想根据文件路径检查文件是否相同,请使用以下方法:

java.nio.file.Files#isSameFile

例如。

Assert.assertTrue(Files.isSameFile(
     new File("some-project\.\hello.txt").toPath(),
     new File("some-project\hello.txt").toPath()
));

1

以下是两种方法的实现:

/**
 * Tests this abstract pathname for equality with the given object.
 * Returns <code>true</code> if and only if the argument is not
 * <code>null</code> and is an abstract pathname that denotes the same file
 * or directory as this abstract pathname.  Whether or not two abstract
 * pathnames are equal depends upon the underlying system.  On UNIX
 * systems, alphabetic case is significant in comparing pathnames; on Microsoft Windows
 * systems it is not.
 *
 * @param   obj   The object to be compared with this abstract pathname
 *
 * @return  <code>true</code> if and only if the objects are the same;
 *          <code>false</code> otherwise
 */
public boolean equals(Object obj) {
    if ((obj != null) && (obj instanceof File)) {
        return compareTo((File)obj) == 0;
    }
    return false;
}

/**
 * Compares two abstract pathnames lexicographically.  The ordering
 * defined by this method depends upon the underlying system.  On UNIX
 * systems, alphabetic case is significant in comparing pathnames; on Microsoft Windows
 * systems it is not.
 *
 * @param   pathname  The abstract pathname to be compared to this abstract
 *                    pathname
 *
 * @return  Zero if the argument is equal to this abstract pathname, a
 *          value less than zero if this abstract pathname is
 *          lexicographically less than the argument, or a value greater
 *          than zero if this abstract pathname is lexicographically
 *          greater than the argument
 *
 * @since   1.2
 */
public int compareTo(File pathname) {
    return fs.compare(this, pathname);
}

0
如果您正在使用Windows,请查看类Win32FileSystem
比较方法如下,因此您的文件对象不同是很正常的。
    public int compare(File f1, File f2) {
      return f1.getPath().compareToIgnoreCase(f2.getPath());
    }

同时将这些行添加到您的代码中

        System.out.println(f1.getPath());
        System.out.println(f2.getPath());

然后它会打印出来

.\hello.txt
hello.txt

因此,它们不相等,因为比较是使用文件对象的路径属性进行的。

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