討論JDK的File.equal()

weixin_33901926發表於2015-12-15

 

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

相關文章