天天看點

Java8 File / FileSystem(一) 源碼解析

目錄

1、separator / pathSeparatorChar

 2、構造方法

 3、isAbsolute / getAbsolutePath / getCanonicalPath 

4、exists / isDirectory / isFile / isHidden / lastModified / length

5、createNewFile / createTempFile / delete / deleteOnExit

6、mkdir / mkdirs

7、list / listFiles / listRoots

     File表示檔案系統中的一個檔案,提供了檔案操作的相關API,其底層實作都是通過FileSystem實作的。FileSystem表示底層作業系統的一個檔案系統,windows下的實作是WinNTFileSystem,類Unix系統下的實作是UnixFileSystem,我們重點關注後者的實作細節。

1、separator / pathSeparatorChar

    這兩個是File類的public static屬性,分别表示路徑分隔符和多個路徑字元串的分隔符,其定義如下:

//路徑分隔符,對應系統屬性file.separator
public static final String separator = "" + separatorChar;

public static final char separatorChar = fs.getSeparator();

//多個路徑字元串的分隔符,對應系統屬性path.separator
public static final String pathSeparator = "" + pathSeparatorChar;

public static final char pathSeparatorChar = fs.getPathSeparator();

//fs表示檔案系統的實作
private static final FileSystem fs = DefaultFileSystem.getFileSystem();


//slash和colon都是UnixFileSystem的私有屬性,在構造方法中完成初始化
public char getSeparator() {
        return slash;
}

public char getPathSeparator() {
        return colon;
    }

public UnixFileSystem() {
        //讀取對應的系統屬性
        slash = AccessController.doPrivileged(
            new GetPropertyAction("file.separator")).charAt(0);
        colon = AccessController.doPrivileged(
            new GetPropertyAction("path.separator")).charAt(0);
        javaHome = AccessController.doPrivileged(
            new GetPropertyAction("java.home"));
    }
           

 window下DefaultFileSystem的實作如下:

Java8 File / FileSystem(一) 源碼解析

類Unix下DefaultFileSystem的實作如下,其源碼在jdk\src\solaris\classes\java\io目錄下:

Java8 File / FileSystem(一) 源碼解析

測試用例如下:

import java.io.File;

public class FileTest {

    public static void main(String[] args) {
        //windows下檔案路徑
//        File file=new File("D:\\export\\data\\test.txt");
        //Linux下的檔案路徑
        File file=new File("/export/data/test.txt");
        System.out.println(file.getName());
        System.out.println("separator->"+File.separator);
        System.out.println("pathSeparator->"+File.pathSeparator);

}
           

window下輸出如下:

Java8 File / FileSystem(一) 源碼解析

Linux下輸出如下:

Java8 File / FileSystem(一) 源碼解析

 2、構造方法

public File(String pathname) {
        if (pathname == null) {
            throw new NullPointerException();
        }
        //将檔案路徑轉換成正常格式
        this.path = fs.normalize(pathname);
        //擷取路徑字首的長度,Linux下如果以/開始則prefixLength是1,否則是0,
        //windows下需要計算包含前面盤符在内的字首的長度
        this.prefixLength = fs.prefixLength(this.path);
    }

public File(String parent, String child) {
        if (child == null) {
            throw new NullPointerException();
        }
        if (parent != null) {
            if (parent.equals("")) {
                //父路徑為空,則使用預設的父路徑,Linux下是/
                //resolve傳回該父路徑下某個子路徑的真實路徑
                this.path = fs.resolve(fs.getDefaultParent(),
                                       fs.normalize(child));
            } else {
                this.path = fs.resolve(fs.normalize(parent),
                                       fs.normalize(child));
            }
        } else {
            this.path = fs.normalize(child);
        }
        this.prefixLength = fs.prefixLength(this.path);
    }

public File(File parent, String child) {
        if (child == null) {
            throw new NullPointerException();
        }
        if (parent != null) {
            //邏輯同上
            if (parent.path.equals("")) {
                this.path = fs.resolve(fs.getDefaultParent(),
                                       fs.normalize(child));
            } else {
                this.path = fs.resolve(parent.path,
                                       fs.normalize(child));
            }
        } else {
            this.path = fs.normalize(child);
        }
        this.prefixLength = fs.prefixLength(this.path);
    }

public File(URI uri) {

        //檢查uri是否符合file類uri 規範
        if (!uri.isAbsolute())
            throw new IllegalArgumentException("URI is not absolute");
        if (uri.isOpaque())
            throw new IllegalArgumentException("URI is not hierarchical");
        String scheme = uri.getScheme();
        if ((scheme == null) || !scheme.equalsIgnoreCase("file"))
            throw new IllegalArgumentException("URI scheme is not \"file\"");
        if (uri.getAuthority() != null)
            throw new IllegalArgumentException("URI has an authority component");
        if (uri.getFragment() != null)
            throw new IllegalArgumentException("URI has a fragment component");
        if (uri.getQuery() != null)
            throw new IllegalArgumentException("URI has a query component");
        String p = uri.getPath();
        if (p.equals(""))
            throw new IllegalArgumentException("URI path component is empty");

        //計算路徑
        p = fs.fromURIPath(p);
        if (File.separatorChar != '/')
            p = p.replace('/', File.separatorChar);
        this.path = fs.normalize(p);
        this.prefixLength = fs.prefixLength(this.path);
    }

//去掉路徑中多餘的/
public String normalize(String pathname) {
        int n = pathname.length();
        char prevChar = 0;
        for (int i = 0; i < n; i++) {
            char c = pathname.charAt(i);
            if ((prevChar == '/') && (c == '/')) 
                //如果是連續兩個//
                return normalize(pathname, n, i - 1);
            prevChar = c;
        }
        //如果最後一個字元是/
        if (prevChar == '/') return normalize(pathname, n, n - 1);
        return pathname;
    }

//計算路徑中字首字元串的長度
public int prefixLength(String pathname) {
        if (pathname.length() == 0) return 0;
        //如果以/開頭則傳回1,否則傳回0
        return (pathname.charAt(0) == '/') ? 1 : 0;
    }

//計算某個父路徑下子路徑的完整路徑
public String resolve(String parent, String child) {
        //child是空,則直接傳回parent
        if (child.equals("")) return parent;
        if (child.charAt(0) == '/') {
            //parent是/
            if (parent.equals("/")) return child;
            return parent + child;
        }
        if (parent.equals("/")) return parent + child;
        return parent + '/' + child;
    }

//去掉路徑中多餘的/
private String normalize(String pathname, int len, int off) {
        if (len == 0) return pathname;
        int n = len;
        //倒着周遊,找到第一個不是/的字元
        while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--;
        if (n == 0) return "/";
        StringBuffer sb = new StringBuffer(pathname.length());
        //截取0到off之間的字元串
        if (off > 0) sb.append(pathname.substring(0, off));
        char prevChar = 0;
        //周遊off到n之間的字元
        for (int i = off; i < n; i++) {
            char c = pathname.charAt(i);
            //如果是連續多個/則跳過後面的/
            if ((prevChar == '/') && (c == '/')) continue;
            sb.append(c);
            prevChar = c;
        }
        return sb.toString();
    }

public String fromURIPath(String path) {
        String p = path;
        //去掉末尾多餘的/
        if (p.endsWith("/") && (p.length() > 1)) {
            // "/foo/" --> "/foo", but "/" --> "/"
            p = p.substring(0, p.length() - 1);
        }
        return p;
    }
           

其中涉及URI的校驗,需要了解URI的格式規範,如下圖:

Java8 File / FileSystem(一) 源碼解析

測試用例如下:

import java.io.File;
import java.net.URI;

public class FileTest {

    public static void main(String[] args) {
        File file=new File("//export//data///test.txt/");
        System.out.println("exist->"+file.exists()+",path->"+file.getPath());
        file=new File("//export/data","/test.txt/");
        System.out.println("exist->"+file.exists()+",path->"+file.getPath());
        file=new File(new File("//export/data///"),"test.txt/");
        System.out.println("exist->"+file.exists()+",path->"+file.getPath());
        //冒号後面不能帶兩個//,會被認為是URI中帶有登入使用者名等權限資訊,校驗失敗
        file=new File(URI.create("file:/export///data//test.txt//"));
        System.out.println("exist->"+file.exists()+",path->"+file.getPath());
    }
}
           

 輸出如下:

Java8 File / FileSystem(一) 源碼解析

 3、isAbsolute / getAbsolutePath / getCanonicalPath 

       isAbsolute判斷目前檔案的路徑是否絕對路徑,getAbsolutePath擷取絕對路徑,如果不是絕對路徑則擷取在user.dir下的絕對路徑;getCanonicalPath 擷取目前檔案路徑的規範化标準化的路徑,會替換掉路徑中包含的./ 和 ../,其實作如下:

//是否絕對路徑
public boolean isAbsolute() {
        return fs.isAbsolute(this);
    }

//擷取目前檔案的絕對路徑
public String getAbsolutePath() {
        return fs.resolve(this);
    }

//擷取目前檔案路徑的規範路徑,會處理掉路徑中的./或者../
public String getCanonicalPath() throws IOException {
        if (isInvalid()) {
            throw new IOException("Invalid file path");
        }
        return fs.canonicalize(fs.resolve(this));
    }

final boolean isInvalid() {
        if (status == null) {
            //如果路徑中包含空字元則認為是無效路徑
            status = (this.path.indexOf('\u0000') < 0) ? PathStatus.CHECKED
                                                       : PathStatus.INVALID;
        }
        return status == PathStatus.INVALID;
    }

//UnixFileSystem的實作
//是否絕對路徑
public boolean isAbsolute(File f) {
        return (f.getPrefixLength() != 0);
    }

//擷取絕對路徑
public String resolve(File f) {
        if (isAbsolute(f)) return f.getPath();
        //如果不是絕對路徑,則擷取在user.dir下的絕對路徑
        return resolve(System.getProperty("user.dir"), f.getPath());
    }

public String canonicalize(String path) throws IOException {
        //父類FileSystem的靜态屬性,預設為true,通過屬性sun.io.useCanonCaches配置
        if (!useCanonCaches) {
            //是本地方法
            return canonicalize0(path);
        } else {
            //cache是ExpiringCache執行個體,一個支援緩存key自動過期的Map
            String res = cache.get(path);
            if (res == null) {
                String dir = null;
                String resDir = null;
                //父類FileSystem的靜态屬性,預設為true,通過屬性sun.io.useCanonPrefixCache配置
                if (useCanonPrefixCache) {
                    // Note that this can cause symlinks that should
                    // be resolved to a destination directory to be
                    // resolved to the directory they're contained in
                    dir = parentOrNull(path);
                    if (dir != null) {
                        //javaHomePrefixCache也是ExpiringCache執行個體
                        resDir = javaHomePrefixCache.get(dir);
                        if (resDir != null) {
                            // Hit only in prefix cache; full path is canonical
                            String filename = path.substring(1 + dir.length());
                            res = resDir + slash + filename;
                            //将解析結果添加到緩存中
                            cache.put(dir + slash + filename, res);
                        }
                    }
                }
                if (res == null) {
                    res = canonicalize0(path);
                    cache.put(path, res);
                    //javaHome是系統屬性java.home的值
                    if (useCanonPrefixCache &&
                        dir != null && dir.startsWith(javaHome)) {
                        resDir = parentOrNull(res);
                        // Note that we don't allow a resolved symlink
                        // to elsewhere in java.home to pollute the
                        // prefix cache (java.home prefix cache could
                        // just as easily be a set at this point)
                        if (resDir != null && resDir.equals(dir)) {
                            File f = new File(res);
                            if (f.exists() && !f.isDirectory()) {
                                javaHomePrefixCache.put(dir, resDir);
                            }
                        }
                    }
                }//第二個res等于null
            }//第一個res等于null
            return res;
        }
    }

//擷取path的父路徑,盡可能避免canonicalize0方法中抛出異常
static String parentOrNull(String path) {
        if (path == null) return null;
        char sep = File.separatorChar;
        int last = path.length() - 1;
        int idx = last;
        //連續的.字元的個數
        int adjacentDots = 0;
        //不是.和分隔符的字元的個數
        int nonDotCount = 0;
        //從最後一個字元開始往前周遊
        while (idx > 0) {
            char c = path.charAt(idx);
            if (c == '.') {
                if (++adjacentDots >= 2) {
                    //路徑中包含..
                    return null;
                }
            } else if (c == sep) {
                if (adjacentDots == 1 && nonDotCount == 0) {
                    //路徑中包含/.
                    return null;
                }
                if (idx == 0 ||
                    idx >= last - 1 ||
                    path.charAt(idx - 1) == sep) {
                    //第一個字元或者倒數的兩個字元包含/,或者連續兩個//
                    return null;
                }
                return path.substring(0, idx);
            } else {
                //不是.和分隔符,計數加1
                ++nonDotCount;
                adjacentDots = 0;
            }
            //周遊下一個字元
            --idx;
        }
        return null;
    }
           

其中父類FileSystem的useCanonCaches和useCanonPrefixCache屬性定義如下:

Java8 File / FileSystem(一) 源碼解析

canonicalize0本地方法的實作在jdk\src\solaris\native\java\io\UnixFileSystem_md.c中,如下圖:

Java8 File / FileSystem(一) 源碼解析

canonicalize C方法的實作在同目錄下的canonicalize_md.c中,可以自行參考,其中的核心就是對路徑做規範化标準化處理的C  realpath函數。測試用例如下:

@Test
    public void test() throws Exception {
        //絕對路徑
        File file=new File("D:\\code\\test.txt");
        System.out.println("isAbsolute:"+file.isAbsolute());
        System.out.println("getAbsolutePath:"+file.getAbsolutePath());
    }

    @Test
    public void test2() throws Exception {
        //相對路徑
        File file=new File("../test.txt");
        System.out.println("isAbsolute:"+file.isAbsolute());
        System.out.println("getAbsolutePath:"+file.getAbsolutePath());
        System.out.println("user.dir:"+System.getProperty("user.dir"));
        System.out.println("getCanonicalPath:"+file.getCanonicalPath());
    }
           

其中第二個測試用例輸出如下:

Java8 File / FileSystem(一) 源碼解析

4、exists / isDirectory / isFile / isHidden / lastModified / length

     這幾個方法都是擷取檔案屬性,判斷檔案是否存在,檔案類型,是否隐藏,最後一次修改時間和檔案的大小,其實作如下:

public boolean exists() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        return ((fs.getBooleanAttributes(this) & FileSystem.BA_EXISTS) != 0);
    }

public boolean isDirectory() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        return ((fs.getBooleanAttributes(this) & FileSystem.BA_DIRECTORY)
                != 0);
    }

public boolean isFile() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        return ((fs.getBooleanAttributes(this) & FileSystem.BA_REGULAR) != 0);
    }

public boolean isHidden() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        return ((fs.getBooleanAttributes(this) & FileSystem.BA_HIDDEN) != 0);
    }

public long lastModified() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return 0L;
        }
        //本地方法實作
        return fs.getLastModifiedTime(this);
    }

//擷取檔案的位元組數,如果不存在則傳回0
public long length() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return 0L;
        }
        //本地方法實作
        return fs.getLength(this);
    }

//UnixFileSystem的實作
public int getBooleanAttributes(File f) {
        //本地方法實作
        int rv = getBooleanAttributes0(f);
        String name = f.getName();
        // 以. 開頭的檔案認為是隐藏檔案
        boolean hidden = (name.length() > 0) && (name.charAt(0) == '.');
        return rv | (hidden ? BA_HIDDEN : 0);
    }
           

其中涉及的FileSystem的幾個常量的定義如下:

Java8 File / FileSystem(一) 源碼解析

其中涉及的本地方法的實作都在UnixFileSystem_md.c中,其核心是擷取檔案屬性的stat64函數,如下:

JNIEXPORT jint JNICALL
Java_java_io_UnixFileSystem_getBooleanAttributes0(JNIEnv *env, jobject this,
                                                  jobject file)
{
    jint rv = 0;

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        int mode;
        if (statMode(path, &mode)) { //傳回true表示檔案存在
            int fmt = mode & S_IFMT;
            //stat的結果轉換
            rv = (jint) (java_io_FileSystem_BA_EXISTS
                  | ((fmt == S_IFREG) ? java_io_FileSystem_BA_REGULAR : 0) //如果是檔案
                  | ((fmt == S_IFDIR) ? java_io_FileSystem_BA_DIRECTORY : 0)); //如果是檔案夾
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getLastModifiedTime(JNIEnv *env, jobject this,
                                                jobject file)
{
    jlong rv = 0;

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        struct stat64 sb;
        if (stat64(path, &sb) == 0) {
            rv = 1000 * (jlong)sb.st_mtime;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}


JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getLength(JNIEnv *env, jobject this,
                                      jobject file)
{
    jlong rv = 0;

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        struct stat64 sb;
        if (stat64(path, &sb) == 0) {
            rv = sb.st_size;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

static jboolean
statMode(const char *path, int *mode)
{
    struct stat64 sb;
    //stat64是标準C函數,用于擷取檔案屬性,ls指令的底層實作就是該函數
    if (stat64(path, &sb) == 0) {
        *mode = sb.st_mode;
        return JNI_TRUE;
    }
    return JNI_FALSE;
}
           

5、createNewFile / createTempFile / delete / deleteOnExit

      createNewFile會建立一個新檔案,如果目前File對象對應的檔案不存在的話,如果存在則傳回false。createTempFile會建立一個在指定目錄下的臨時檔案,臨時檔案的檔案名是通過prefix、随機數、suffix生成,傳回的File對應的檔案肯定是原來不存在的,注意createTempFile本身并不删除臨時檔案,需要程式顯示調用delete方法删除或者放在作業系統臨時目錄下,由作業系統負責删除。delete和deleteOnExit都是用于删除檔案,差別在于前者是立即删除,後者是在JVM退出時通過回調鈎子方法執行的删除,實際的删除動作還是delete方法完成的。

//如果目标路徑的檔案不存在則建立一個新的
public boolean createNewFile() throws IOException {
        SecurityManager security = System.getSecurityManager();
        if (security != null) security.checkWrite(path);
        if (isInvalid()) {
            throw new IOException("Invalid file path");
        }
        //是一個本地方法
        return fs.createFileExclusively(path);
    }

//在指定目錄下建立一個臨時檔案,檔案名由prefix 和 suffix指定,不宜過長,如果超長會被自動截斷
//如果suffix為null,則預設為.tmp,如果directory為null,則預設在系統的臨時目錄下建立檔案,Unix下是/tmp或者/var/tmp
public static File createTempFile(String prefix, String suffix,
                                      File directory)
        throws IOException
    {
        if (prefix.length() < 3) //字首不小于3
            throw new IllegalArgumentException("Prefix string too short");
        if (suffix == null)
            suffix = ".tmp"; //字尾預設為.tmp
        
        //檔案路徑預設為系統的臨時檔案夾目錄
        File tmpdir = (directory != null) ? directory
                                          : TempDirectory.location();
        SecurityManager sm = System.getSecurityManager();
        File f;
        do {
            //生成一個随機檔案名的File對象,此時未實際建立檔案
            f = TempDirectory.generateFile(prefix, suffix, tmpdir);

            if (sm != null) {
                try {
                    //檢查通路權限
                    sm.checkWrite(f.getPath());
                } catch (SecurityException se) {
                    // don't reveal temporary directory location
                    if (directory == null)
                        throw new SecurityException("Unable to create temporary file");
                    throw se;
                }
            }
            //檢查檔案是否存在,如果存在則繼續生成一個新檔案名的檔案
        } while ((fs.getBooleanAttributes(f) & FileSystem.BA_EXISTS) != 0);

        if (!fs.createFileExclusively(f.getPath())) //如果建立檔案失敗,則抛出異常
            throw new IOException("Unable to create temporary file");

        return f;
    }

public static File createTempFile(String prefix, String suffix)
        throws IOException
    {
        return createTempFile(prefix, suffix, null);
    }


public boolean delete() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkDelete(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.delete(this);
    }

public void deleteOnExit() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkDelete(path);
        }
        if (isInvalid()) {
            return;
        }
        //在JVM退出時執行删除
        DeleteOnExitHook.add(path);
    }

//File的靜态内部類
private static class TempDirectory {
        private TempDirectory() { }

        //擷取臨時檔案夾路徑
        private static final File tmpdir = new File(AccessController
            .doPrivileged(new GetPropertyAction("java.io.tmpdir")));

        //傳回系統臨時檔案夾路徑    
        static File location() {
            return tmpdir;
        }

        //使用SecureRandom而非Random生成随機數,可避免因為種子問題導緻生成的随機數序列一緻的問題
        private static final SecureRandom random = new SecureRandom();
        //在指定目錄下生成一個臨時檔案
        static File generateFile(String prefix, String suffix, File dir)
            throws IOException
        {
            long n = random.nextLong();
            if (n == Long.MIN_VALUE) {
                n = 0;      // corner case
            } else {
                n = Math.abs(n);
            }

            //擷取字首,這裡通過File的構造方法去掉了prefix中可能的多餘的/
            prefix = (new File(prefix)).getName();
                
            //生成随機的檔案名
            String name = prefix + Long.toString(n) + suffix;
            File f = new File(dir, name);
            //如果name中包含多餘的字元或者是非法路徑則抛出異常
            if (!name.equals(f.getName()) || f.isInvalid()) {
                if (System.getSecurityManager() != null)
                    throw new IOException("Unable to create temporary file");
                else
                    throw new IOException("Unable to create temporary file, " + f);
            }
            return f;
        }
    }

//UnixFileSystem實作
public boolean delete(File f) {
        //清空路徑解析的緩存
        cache.clear();
        javaHomePrefixCache.clear();
        //本地方法實作
        return delete0(f);
    }
           

其中涉及的本地方法的實作都在UnixFileSystem_md.c中,其核心是負責打開和建立檔案的open64函數和删除檔案的remove函數,如下:

JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createFileExclusively(JNIEnv *env, jclass cls,
                                                  jstring pathname)
{
    jboolean rv = JNI_FALSE;

    WITH_PLATFORM_STRING(env, pathname, path) {
        FD fd;
        /* The root directory always exists */
        if (strcmp (path, "/")) {
            //O_CREAT下如果檔案不存在則建立
            fd = handleOpen(path, O_RDWR | O_CREAT | O_EXCL, 0666);
            if (fd < 0) {
                if (errno != EEXIST) //如果不是因為檔案已存在導緻的失敗,則抛出異常
                    JNU_ThrowIOExceptionWithLastError(env, path);
            } else {
                if (close(fd) == -1) //關閉fd失敗,抛出異常
                    JNU_ThrowIOExceptionWithLastError(env, path);
                rv = JNI_TRUE;
            }
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}


JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_delete0(JNIEnv *env, jobject this,
                                    jobject file)
{
    jboolean rv = JNI_FALSE;

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        //remove是C函數,用于移除檔案
        if (remove(path) == 0) {//移除成功,傳回true
            rv = JNI_TRUE;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

FD
handleOpen(const char *path, int oflag, int mode) {
    FD fd;
    //open64是一個C函數,用于打開檔案,根據oflag不同會有不同的行為
    RESTARTABLE(open64(path, oflag, mode), fd);
    if (fd != -1) {
        //open64執行成功
        struct stat64 buf64;
        int result;
        //fstat64是C函數,擷取檔案屬性
        RESTARTABLE(fstat64(fd, &buf64), result);
        if (result != -1) {
            if (S_ISDIR(buf64.st_mode)) {
                //如果目錄檔案存在且是檔案夾
                close(fd);
                errno = EISDIR;
                fd = -1;
            }
        } else {
            //執行fstat64異常
            close(fd);
            fd = -1;
        }
    }
    return fd;
}
           

測試用例如下:

@Test
    public void test3() throws Exception {
        File file=new File("D:\\code\\test2.txt");
        System.out.println("exist->"+file.exists());
        System.out.println("createNewFile->"+file.createNewFile());
        System.out.println("exist after create->"+file.exists());
        System.out.println("createNewFile two->"+file.createNewFile());
        System.out.println("delete ->"+file.delete());
        System.out.println("delete two->"+file.delete());
    }

    @Test
    public void test4() throws Exception {
        for(int i=0;i<5;i++) {
            //字尾預設是.tmp,檔案路徑預設是系統的臨時目錄
            File file = File.createTempFile("tst", null);
            System.out.println("exists->" + file.exists() + ",path->" + file.getPath());
        }
    }
           

6、mkdir / mkdirs

      mkdir用于建立一個檔案夾,mkdirs用于建立從父目錄到目前目錄的多個檔案夾,如果建立失敗則傳回false,如果建立成功或者本身已經存在則傳回true,其實作如下:

//建立檔案夾,如果建立失敗則傳回false
public boolean mkdir() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
        //本地方法實作
        return fs.createDirectory(this);
    }

//建立多個檔案夾,如果建立失敗則傳回false
 public boolean mkdirs() {
        if (exists()) { //檔案已存在
            return false;
        }
        if (mkdir()) { //直接建立檔案夾失敗
            return true;
        }
        File canonFile = null;
        try {
            //擷取标準化路徑對應的檔案
            canonFile = getCanonicalFile();
        } catch (IOException e) {
            return false;
        }
        //擷取父檔案夾對應的File
        File parent = canonFile.getParentFile();
        //parent不為空,則調用其mkdirs,此處實際是一個遞歸
        return (parent != null && (parent.mkdirs() || parent.exists()) &&
                canonFile.mkdir());
    }

public File getCanonicalFile() throws IOException {
        String canonPath = getCanonicalPath();
        return new File(canonPath, fs.prefixLength(canonPath));
    }
           

其中createDirectory本地方法的實作在UnixFileSystem_md.c中,其核心是mkdir函數,如下:

JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createDirectory(JNIEnv *env, jobject this,
                                            jobject file)
{
    jboolean rv = JNI_FALSE;
    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        //0777表示建立的檔案是所有使用者都可讀可寫可執行的
        if (mkdir(path, 0777) == 0) {
            rv = JNI_TRUE;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}
           

7、list / listFiles / listRoots

      list和listFiles都是擷取目前File下的子檔案或者子目錄,差別在于前者傳回檔案名,後者傳回檔案名對應的File對象,可以添加FilenameFilter 或者FileFilter 實作過濾掉不滿足條件的檔案。listRoots傳回根目錄對應的File,Unix下傳回 / 對應的File對象,Windows下傳回所有磁盤分區對應的File對象。

public String[] list() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) { //路徑無效,傳回null
            return null;
        }
        //本地方法
        return fs.list(this);
    }

public String[] list(FilenameFilter filter) {
        String names[] = list();
        if ((names == null) || (filter == null)) {
            return names;
        }
        List<String> v = new ArrayList<>();
        for (int i = 0 ; i < names.length ; i++) {
            //執行過濾邏輯
            if (filter.accept(this, names[i])) {
                v.add(names[i]);
            }
        }
        return v.toArray(new String[v.size()]);
    }

public File[] listFiles() {
        String[] ss = list();
        if (ss == null) return null;
        int n = ss.length;
        File[] fs = new File[n];
        for (int i = 0; i < n; i++) {
            //将list方法檔案的檔案名轉換成File對象
            fs[i] = new File(ss[i], this);
        }
        return fs;
    }

public File[] listFiles(FilenameFilter filter) {
        String ss[] = list();
        if (ss == null) return null;
        ArrayList<File> files = new ArrayList<>();
        for (String s : ss)
            //執行過濾邏輯,将檔案名轉換成File對象
            if ((filter == null) || filter.accept(this, s))
                files.add(new File(s, this));
        return files.toArray(new File[files.size()]);
    }

public File[] listFiles(FileFilter filter) {
        String ss[] = list();
        if (ss == null) return null;
        ArrayList<File> files = new ArrayList<>();
        for (String s : ss) {
            File f = new File(s, this);
            //執行過濾邏輯,将檔案名轉換成File對象
            if ((filter == null) || filter.accept(f))
                files.add(f);
        }
        return files.toArray(new File[files.size()]);
    }

public static File[] listRoots() {
        return fs.listRoots();
    }

//UnixFileSystem的實作
public File[] listRoots() {
        try {
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkRead("/");
            }
            return new File[] { new File("/") };
        } catch (SecurityException x) {
            return new File[0];
        }
    }
           

其中list本地方法的實作在 UnixFileSystem_md.c中,其核心是打開目錄檔案的opendir函數,負責逐一讀取目錄下子目錄或者子檔案的readdir64_r函數以及關閉目錄對象Dir的closedir函數,如下:

JNIEXPORT jobjectArray JNICALL
Java_java_io_UnixFileSystem_list(JNIEnv *env, jobject this,
                                 jobject file)
{
    DIR *dir = NULL;
    struct dirent64 *ptr;
    struct dirent64 *result;
    int len, maxlen;
    jobjectArray rv, old;
    jclass str_class;
    
    //擷取String對應的Class
    str_class = JNU_ClassString(env);
    CHECK_NULL_RETURN(str_class, NULL);

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        //opendir是C函數,打開某個檔案目錄
        dir = opendir(path);
    } END_PLATFORM_STRING(env, path);
    //目錄不存在,傳回null
    if (dir == NULL) return NULL;
    
    //建立一個dirent64數組
    ptr = malloc(sizeof(struct dirent64) + (PATH_MAX + 1));
    if (ptr == NULL) {
        //記憶體不足,抛出異常
        JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
        //closedir是C函數,關閉目錄
        closedir(dir);
        return NULL;
    }

    /* Allocate an initial String array */
    len = 0;
    maxlen = 16;
    //建立一個初始長度為16的String數組
    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
    //建立失敗,跳轉到goto
    if (rv == NULL) goto error;

    /* Scan the directory */
    //readdir64_r是C函數,用于讀取下一個目錄,讀取的結果放到ptr數組中
    while ((readdir64_r(dir, ptr, &result) == 0)  && (result != NULL)) {
        jstring name;
        if (!strcmp(ptr->d_name, ".") || !strcmp(ptr->d_name, "..")) //跳過. 和 ..
            continue;
        if (len == maxlen) {
            //數組滿了,執行擴容
            old = rv;
            //建立一個擴容一倍的數組
            rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
            if (rv == NULL) goto error;
            //數組拷貝
            if (JNU_CopyObjectArray(env, rv, old, len) < 0) goto error;
            //删除本地引用old
            (*env)->DeleteLocalRef(env, old);
        }
        //建立Java字元串,d_name表示檔案名
        name = JNU_NewStringPlatform(env, ptr->d_name);

        if (name == NULL) goto error;
        //儲存到Java數組中,len加1,并删除本地引用
        (*env)->SetObjectArrayElement(env, rv, len++, name);
        (*env)->DeleteLocalRef(env, name);
    }
    //關閉目錄,釋放記憶體
    closedir(dir);
    free(ptr);

    /* 根據實際的結果大小,重新建立一個String數組 */
    old = rv;
    rv = (*env)->NewObjectArray(env, len, str_class, NULL);
    if (rv == NULL) {
        return NULL;
    }
    //數組拷貝
    if (JNU_CopyObjectArray(env, rv, old, len) < 0) {
        return NULL;
    }
    return rv;

 error:
    closedir(dir);
    free(ptr);
    return NULL;
}
           

 測試用例如下:

@Test
 public void test5() throws Exception {
        File file=new File("D:\\code");
        System.out.println(Arrays.toString(file.list()));
        System.out.println(Arrays.toString(file.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                System.out.println("FilenameFilter dir->"+dir+",name->"+name);
                return name.contains("priv");
            }
        })));
        System.out.println(Arrays.toString(file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                System.out.println("FileFilter file->"+pathname.getPath());
                return pathname.getPath().contains("pub");
            }
        })));
    }
           

 結果如下:

Java8 File / FileSystem(一) 源碼解析