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的實現如下:

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

測試用例如下:

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下輸出如下:

Linux下輸出如下:

 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的格式規範,如下圖:

測試用例如下:

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());
    }
}

 輸出如下:

 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屬性定義如下:

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

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());
    }

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

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的幾個常量的定義如下:

其中涉及的本地方法的實現都在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");
            }
        })));
    }

 結果如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章