我们一般比较两个文件对象是否同一个文件,一般会使用java.io.File.equal()。这里所说的equal()并不是比较文件内容是否一样,而是看两个文件对象是否指向同一个文件。
File的equal()方法,实际上调用了当前文件系统FileSystem的compareTo()。
public boolean equals(Object obj) { if ((obj != null) && (obj instanceof File)) { return compareTo((File)obj) == 0; } return false; } static private FileSystem fs = FileSystem.getFileSystem(); public int compareTo(File pathname) { return fs.compare(this, pathname); }
我们发现,java.io.FileSystem中没有对Unix/Linux的实现,只有Win32FileSystem,所以都是默认调用的这个实现类。 它对文件的比较,其实就是对文件名和绝对路径的比较。如果两个File对象有相同的getPath(),就认为他们是同一个文件。而且能看出来,Windows是不区分大小写的。
如下面的java.io.Win32FileSystem.compare()。
public int compare(File f1, File f2) { return f1.getPath().compareToIgnoreCase(f2.getPath()); }
这样通过比较绝对路径来检验两个对象是否指向同一个文件的方法,能适用大部分的情况,但也要小心。比如说,在Linux下面,文件名对大小写是敏感的,就不能ignore了。而且通过硬链接建立的文件,实质还是指向同一个文件的,但是在File.equal()中却为false。
所以在JDK1.7后引入了工具类java.nio.file.Files,可以通过isSameFile()来判断两个文件对象是否指向同一个文件。
public boolean isSameFile(Path path, Path path2) throws IOException { return provider(path).isSameFile(path, path2); } private static FileSystemProvider provider(Path path) { return path.getFileSystem().provider(); }
他是获取当前系统的provider,再调用其isSameFile()来校验的。下面的FileSystem的实现层次结构:
java.nio.file.spi.FileSystemProvider
sun.nio.fs.AbstractFileSystemProvider
sun.nio.fs.UnixFileSystemProvider
sun.nio.fs.LinuxFileSystemProvider
sun.nio.fs.WindowsFileSystemProvider
我们先看看UnixFileSystemProvider.isSameFile() 是怎么实现的:
public boolean isSameFile(Path obj1, Path obj2) throws IOException { UnixPath file1 = UnixPath.toUnixPath(obj1); if (file1.equals(obj2)) return true; file1.checkRead();file2.checkRead(); UnixFileAttributes attrs1 = UnixFileAttributes.get(file1, true); UnixFileAttributes attrs2 = UnixFileAttributes.get(file2, true); return attrs1.isSameFile(attrs2); }
他先调用了UnixPath.equal(),然后检查两个文件的可读性,最后再调用了UnixFileAttributes.isSameFile()。很显然,他会先检查两个文件的绝对路径是否相同(大小写敏感),如果相同的话,就认为两者是同一个文件;如果不同,再检查两个文件的iNode号。这是Unix文件系统的特点,文件是通过iNode来标识的,只要iNode号相同,就说明指向同一个文件。所以能用在判断两个硬链接是否指向同一个文件。
------------------------UnixPath------------------------
public boolean equals(Object ob) { if ((ob != null) && (ob instanceof UnixPath)) return compareTo((Path)ob) == 0; // compare two path return false; } public int compareTo(Path other) { int len1 = path.length; int len2 = ((UnixPath) other).path.length; int n = Math.min(len1, len2); byte v1[] = path; byte v2[] = ((UnixPath) other).path; int k = 0; while (k < n) { int c1 = v1[k] & 0xff; int c2 = v2[k] & 0xff; if (c1 != c2) return c1 - c2; } return len1 - len2; }
------------------------UnixFileAttributes------------------------
boolean isSameFile(UnixFileAttributes attrs) { return ((st_ino == attrs.st_ino) && (st_dev == attrs.st_dev)); }
而对于Windows系统,也是大同小异,来看看WindowsFileSystemProvider.isSameFile(),WindowsPath.equal()和 WindowsFileAttributes.isSameFile()。
都是先判断文件绝对路径(忽略大小写),如果相等就认为是同一个文件;如果不等就再进行底层判断,Windows底层文件的判断是检查磁盘号是否相等来完成的。
------------------------ WindowsFileSystemProvider------------------------
public boolean isSameFile(Path obj1, Path obj2) throws IOException { WindowsPath file1 = WindowsPath.toWindowsPath(obj1); if (file1.equals(obj2)) return true; file1.checkRead();file2.checkRead(); WindowsFileAttributes attrs1 =WindowsFileAttributes.readAttributes(h1); WindowsFileAttributes attrs2 =WindowsFileAttributes.readAttributes(h2); return WindowsFileAttributes.isSameFile(attrs1, attrs2); }
------------------------ WindowsPath ------------------------
public boolean equals(Object obj) { if ((obj != null) && (obj instanceof WindowsPath)) return compareTo((Path)obj) == 0; return false; } public int compareTo(Path obj) { if (obj == null) throw new NullPointerException(); String s1 = path; String s2 = ((WindowsPath)obj).path; int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) return c1 - c2; } } return n1 - n2; }
------------------------ WindowsFileAttributes------------------------
static boolean isSameFile(WindowsFileAttributes attrs1, WindowsFileAttributes attrs2) { // volume serial number and file index must be the same return (attrs1.volSerialNumber == attrs2.volSerialNumber) && (attrs1.fileIndexHigh == attrs2.fileIndexHigh) && (attrs1.fileIndexLow == attrs2.fileIndexLow); }
这样一比较就清晰了,如果只是对比文件的绝对路径是否相等(不是内容),可以放心使用File.equal()。而如果要比较在OS中是否指向同一个文件,可以使用Files.isSameFile(),它考虑到了不同文件系统的差异。同时,我们通过观察这两种系统校验规则的不同实现,也能窥视到不同OS文件系统的差异,如果你有兴趣,可以进一步深入研究哦!
最后,付上一个OpenJava的源码地址,你可以在里面找到JDK引用的很多sun.xxx.xxx的源码。例如上面提到的一系列sun.nio.fs.xxx。http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/sun/awt/shell/ShellFolder.java#ShellFolder.compareTo%28java.io.File%29