Java,Linux:如何检测两个java.io.Files是否引用相同的物理文件

时间:2011-05-04 12:18:24

标签: java file unix equals

我正在寻找一种有效的方法来检测两个java.io.File是否引用相同的物理文件。根据文档,File.equals() 应该完成这项工作:

  

测试此抽象路径名   与给定对象相等。   当且仅当时,返回true   参数不是null并且是一个   表示的抽象路径名   与此相同的文件或目录   抽象路径名。

但是,假设安装在/ media / truecrypt1的FAT32分区(实际上是TrueCrypt容器):

new File("/media/truecrypt1/File").equals(new File("/media/truecrypt1/file")) == false

你会说这符合规格吗?在这种情况下,如何解决这个问题?

更新:感谢评论者,对于Java 7,我发现java.io.Files.isSameFile()对我有用。

8 个答案:

答案 0 :(得分:14)

@Joachim的评论中的答案通常是正确的。确定两个File对象是否引用相同OS文件的方法是使用getCanonicalFile()或getCanonicalPath()。 javadoc说:

  

“规范路径名是绝对路径名。[...]表示现有文件或目录的每个路径名都有一个唯一的规范形式。”

所以以下应该工作:

File f1 = new File("/media/truecrypt1/File");  // different capitalization ...
File f2 = new File("/media/truecrypt1/file");  // ... but same OS file (on Windows)
if (f1.getCanonicalPath().equals(f2.getCanonicalPath())) {
    System.out.println("Files are equal ... no kittens need to die.");
}

但是,您似乎正在查看在UNIX / Linux上安装的FAT32文件系统。 AFAIK,Java并不知道这种情况正在发生,并且只是对文件名应用通用的UNIX / Linux规则......在这种情况下给出了错误的答案。

如果这是真正发生的事情,我认为纯Java 6中没有可靠的解决方案。但是,

  • 你可以做一些毛茸茸的JNI东西;例如获取文件描述符编号,然后在本机代码中,使用fstat(2)系统调用来获取两个文件的设备和inode编号并进行比较。

  • 如果您首先在路径上调用java.nio.file.Path.equals(Object)来解析符号链接,那么Java 7 resolve()似乎可能给出正确的答案。 (从javadoc中可以看出,Linux上的每个挂载的文件系统是否都对应于不同的FileSystem对象。

  • Java 7教程有this section查看两个Path对象是否属于同一文件...建议使用java.nio.file.Files.isSameFile(Path, Path)


  

你会说这符合规范吗?

不,是的。

  • 从某种意义上说,getCanonicalPath()方法没有为每个现有的OS文件返回相同的值...这是您对读取javadoc的期望。

  • 从技术意义上讲,是的,Java代码库(不是javadoc)是理论上和实践中的最终规范。

答案 1 :(得分:3)

您可以尝试获取exclusive write lock on the file,看看是否失败:

boolean isSame;
try {
   FileOutputStream file1 = new FileOutputStream (file1);
   FileOutputStream file2 = new FileOutputStream (file2);
   FileChannel channel1 = file1.getChannel();
   FileChannel channel2 = file2.getChannel();
   FileLock fileLock1 = channel1.tryLock();
   FileLock fileLock2 = channel2.tryLock();
   isSame = fileLock2 != null;
} catch(/*appropriate exceptions*/) {
   isSame = false;
} finally {
   fileLock1.unlock();
   fileLock2.unlock();
   file1.close();
   file2.close();
   ///cleanup etc...
}
System.out.println(file1 + " and " + file2 + " are " + (isSame?"":"not") + " the same");

这并不总是保证是正确的 - 因为另一个进程可能已经获得锁定,因此失败了。但至少这并不需要你向外部进程发出外壳。

答案 2 :(得分:2)

同一文件有可能有两条路径(例如,通过网络\\localhost\file,而\\127.0.0.1\file将引用具有不同路径的同一文件)。 我会去比较两个文件的摘要,以确定它们是否相同。像这样的东西

public static void main(String args[]) {
    try {
        File f1 = new File("\\\\79.129.94.116\\share\\bots\\triplon_bots.jar");
        File f2 = new File("\\\\triplon\\share\\bots\\triplon_bots.jar");
        System.out.println(f1.getCanonicalPath().equals(f2.getCanonicalPath()));
        System.out.println(computeDigestOfFile(f1).equals(computeDigestOfFile(f2)));
    }
    catch(Exception e) {
        e.printStackTrace();
    }
}

private static String computeDigestOfFile(File f) throws Exception {
    MessageDigest md = MessageDigest.getInstance("MD5");
    InputStream is = new FileInputStream(f);
    try {
        is = new DigestInputStream(is, md);
        byte[] buffer = new byte[1024];
        while(is.read(buffer) != -1) {
            md.update(buffer);
        }
    }
    finally {
        is.close();
    }
    return new BigInteger(1,md.digest()).toString(16);
}

输出

false
true

这种方法当然比任何类型的路径比较慢得多,它还取决于文件的大小。另一个可能的副作用是两个文件被认为等于与它们的位置无关。

答案 3 :(得分:2)

添加了Files.isSameFile方法以实现这种用法 - 也就是说,您要检查两个不相等的路径是否找到同一个文件。

答案 4 :(得分:1)

在* nix系统上,大写 具有重要性。 fileFilefiLe不同。

答案 5 :(得分:1)

equals()的API文档(在您的引用之后):

  

在UNIX系统上,字母大小写是   在比较路径名方面具有重要意义上   它不是Microsoft Windows系统。

答案 6 :(得分:1)

您可以尝试

的Runtime.exec()
ls -i /fullpath/File # extract the inode number.
df /fullpath/File # extract the "Mounted on" field.

如果挂载点和“inode”编号相同,则无论您是否具有符号链接或不区分大小写的文件系统,它们都是相同的文件。

甚至

bash test "file1" -ef "file2"
  

FILE1和FILE2具有相同的设备和inode编号

答案 7 :(得分:1)

测试两个文件名是否引用相同的底层文件系统对象的传统Unix方法是stat它们并测试它们是否具有相同的[dev,ino]对。

然而,假设没有多余的坐骑。如果允许这些,你必须采取不同的方式。