我正在寻找一种有效的方法来检测两个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()
对我有用。
答案 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系统上,大写 具有重要性。 file
与File
或fiLe
不同。
答案 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]
对。
然而,假设没有多余的坐骑。如果允许这些,你必须采取不同的方式。