我們一般比較兩個檔案中的物件是相同的檔案,通常使用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