我正在寻找一种有效的方法来检测两个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系统上,大小写很重要。file
与File
或fiLe
不一样
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]
对。
并不假设没有冗余挂载。如果这些都是允许的,你就得换个方式了