Java、Linux:如何检测两个Java .io. file是否指向同一个物理文件



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

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

然而,给定一个FAT32分区(实际上是一个TrueCrypt容器)挂载在/media/truecrypt1:

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

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

更新:感谢评论,对于Java 7,我找到了适合我的java.io.Files.isSameFile()

@Joachim评论中的答案通常是正确的。判断两个File对象是否指向同一个操作系统文件的方法是使用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文件系统。据我所知,Java并不知道这正在发生,它只是应用了通用的UNIX/Linux文件名规则…在这种情况下给出错误的答案。

如果这是真的发生了,我不认为在纯Java 6中有一个可靠的解决方案。然而,

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

  • Java 7 java.nio.file.Path.equals(Object)看起来像它可能给出正确的答案,如果您首先在路径上调用resolve()来解析符号链接。(从javadoc来看,Linux上每个挂载的文件系统是否对应一个不同的FileSystem对象有点不清楚。)

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


你说这符合规格吗?

No and yes。

  • 没有,因为getCanonicalPath()方法没有为每个现有的操作系统文件返回相同的值…这是您在阅读javadoc时所期望的。

  • 是的,从技术意义上讲,Java代码库(不是javadoc)是最终规范…

您可以尝试获取文件上的独占写锁,看看是否失败:

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");

这并不能保证总是正确的——因为另一个进程可能已经获得了锁,从而导致失败。但至少这不需要您向外部流程支付费用。

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

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

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

文件。isSameFile方法就是为这种用法而添加的——也就是说,您想要检查两个不相等的路径是否位于同一文件。

在*nix系统上,大小写很重要。fileFilefiLe不一样

equals()的API文档说(就在你的报价之后):

在UNIX系统上,字母大小写为对路径名的比较有意义;在微软Windows系统,它不是。

您可以尝试Runtime.exec() of

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编号

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

但是,

并不假设没有冗余挂载。如果这些都是允许的,你就得换个方式了

相关内容

  • 没有找到相关文章

最新更新