浅谈JDK的File.equal()

 

         我们一般比较两个文件对象是否同一个文件,一般会使用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

你可能感兴趣的:(jdk,File,Files,equal,isSameFile)