Java8 File / FileSystem(二) 源碼解析

  目錄

1、renameTo / setLastModified

2、setReadOnly / setWritable / setReadable / setExecutable / canExecute / canRead / canWrite

3、getTotalSpace / getFreeSpace / getUsableSpace

4、compareTo / equals / hashCode

5、ExpiringCache

6、DeleteOnExitHook 


     本篇博客繼續上一篇《Java8 File / FileSystem(一) 源碼解析》講解File其他方法的源碼實現。

1、renameTo / setLastModified

public boolean renameTo(File dest) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            //校驗訪問權限
            security.checkWrite(path);
            security.checkWrite(dest.path);
        }
        if (dest == null) {
            throw new NullPointerException();
        }
        //校驗路徑合法性
        if (this.isInvalid() || dest.isInvalid()) {
            return false;
        }
        return fs.rename(this, dest);
    }

//UnixFileSystem的實現
public boolean rename(File f1, File f2) {
        //清除路徑解析的緩存
        cache.clear();
        javaHomePrefixCache.clear();
        //本地方法
        return rename0(f1, f2);
    }

public boolean setLastModified(long time) {
        if (time < 0) throw new IllegalArgumentException("Negative time");
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            //校驗訪問權限
            security.checkWrite(path);
        }
        if (isInvalid()) { //校驗路徑合法性
            return false;
        }
        //本地方法實現
        return fs.setLastModifiedTime(this, time);
    }

 涉及的本地方法實現都在UnixFileSystem_md.c中,其核心是用於文件重命名的rename函數和用於修改文件最後修改時間的utimes函數,如下:

JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_rename0(JNIEnv *env, jobject this,
                                    jobject from, jobject to)
{
    jboolean rv = JNI_FALSE;

    WITH_FIELD_PLATFORM_STRING(env, from, ids.path, fromPath) {
        WITH_FIELD_PLATFORM_STRING(env, to, ids.path, toPath) {
            //調用rename函數,如果toPath在另一個目錄下,則相當於移動文件並重命名,如果toPath已存在則rename失敗
            if (rename(fromPath, toPath) == 0) {
                rv = JNI_TRUE;
            }
        } END_PLATFORM_STRING(env, toPath);
    } END_PLATFORM_STRING(env, fromPath);
    return rv;
}

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

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        struct stat64 sb;
        //調用stat64函數獲取文件屬性
        if (stat64(path, &sb) == 0) {
            struct timeval tv[2];

            /* 之前的文件修改時間 */
            tv[0].tv_sec = sb.st_atime;
            tv[0].tv_usec = 0;

            /* 指定的文件修改時間,time是毫秒數 */
            tv[1].tv_sec = time / 1000;  //轉換成秒數
            tv[1].tv_usec = (time % 1000) * 1000; //上述秒數對應的毫秒數
            //調用utimes函數修改文件屬性
            if (utimes(path, tv) == 0)
                rv = JNI_TRUE; //修改成功,返回true
        }
    } END_PLATFORM_STRING(env, path);

    return rv;
}

 測試用例如下:

@Test
    public void test6() throws Exception {
        File file=new File("D:\\code\\test.txt");
        Calendar calendar=Calendar.getInstance();
        calendar.add(Calendar.HOUR,-1);
        System.out.println("setLastModified:"+file.setLastModified(calendar.getTimeInMillis()));
        System.out.println("lastModified:"+file.lastModified());
        //如果目標路徑對應的文件已存在,則返回false
        System.out.println("renameTo:"+file.renameTo(new File("D:\\test2.txt")));
    }

2、setReadOnly / setWritable / setReadable / setExecutable / canExecute / canRead / canWrite

      這幾個都是獲取或者修改文件的可讀可寫可執行權限的,如下:

public boolean canRead() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        //本地方法實現
        return fs.checkAccess(this, FileSystem.ACCESS_READ);
    }

public boolean canWrite() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.checkAccess(this, FileSystem.ACCESS_WRITE);
    }

public boolean canExecute() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkExec(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.checkAccess(this, FileSystem.ACCESS_EXECUTE);
    }

public boolean setReadOnly() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
         //本地方法實現
        return fs.setReadOnly(this);
    }

//writable爲true表示可寫,爲false表示不可寫,ownerOnly爲true,表示是否可寫只針對於文件所有者,爲false則適用於所有人
//如果底層的文件系統不支持對不同用戶控制權限,則此參數無意義
public boolean setWritable(boolean writable, boolean ownerOnly) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
         //本地方法實現
        return fs.setPermission(this, FileSystem.ACCESS_WRITE, writable, ownerOnly);
    }

public boolean setWritable(boolean writable) {
        return setWritable(writable, true);
    }

//參數的含義同上,兩個都爲true,表示只有文件的所有者可以讀取該文件,其他用戶無法讀取
public boolean setReadable(boolean readable, boolean ownerOnly) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.setPermission(this, FileSystem.ACCESS_READ, readable, ownerOnly);
    }

public boolean setReadable(boolean readable) {
        return setReadable(readable, true);
    }

/參數的含義同上,兩個都爲true,表示只有文件的所有者可以執行該文件,其他用戶無法執行
public boolean setExecutable(boolean executable, boolean ownerOnly) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.setPermission(this, FileSystem.ACCESS_EXECUTE, executable, ownerOnly);
    }

public boolean setExecutable(boolean executable) {
        return setExecutable(executable, true);
    }

 涉及的本地方法實現都在UnixFileSystem_md.c中,其核心是用於查詢文件是否具有指定權限的access函數和用於修改文件訪問權限的chmod函數,如下:

JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_checkAccess(JNIEnv *env, jobject this,
                                        jobject file, jint a)
{
    jboolean rv = JNI_FALSE;
    int mode = 0;
    //將FileSystem定義的常量轉換成C中的枚舉
    switch (a) {
    case java_io_FileSystem_ACCESS_READ:
        mode = R_OK;
        break;
    case java_io_FileSystem_ACCESS_WRITE:
        mode = W_OK;
        break;
    case java_io_FileSystem_ACCESS_EXECUTE:
        mode = X_OK;
        break;
    default: assert(0);
    }
    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        //調用access函數,獲取文件的訪問權限類型
        if (access(path, mode) == 0) {
            rv = JNI_TRUE;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}


JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_setPermission(JNIEnv *env, jobject this,
                                          jobject file,
                                          jint access,
                                          jboolean enable,
                                          jboolean owneronly)
{
    jboolean rv = JNI_FALSE;

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        int amode = 0;
        int mode;
        //轉換成C中定義的枚舉
        switch (access) {
        case java_io_FileSystem_ACCESS_READ:
            if (owneronly)
                amode = S_IRUSR; //所有者可讀
            else
                //所有者,用戶組,其他用戶組,即所有用戶都可讀
                amode = S_IRUSR | S_IRGRP | S_IROTH;
            break;
        case java_io_FileSystem_ACCESS_WRITE:
            if (owneronly)
                amode = S_IWUSR;
            else
                amode = S_IWUSR | S_IWGRP | S_IWOTH;
            break;
        case java_io_FileSystem_ACCESS_EXECUTE:
            if (owneronly)
                amode = S_IXUSR;
            else
                amode = S_IXUSR | S_IXGRP | S_IXOTH;
            break;
        default:
            assert(0);
        }
        //調用stat64獲取原來的文件權限
        if (statMode(path, &mode)) {
            if (enable) //爲true,表示開啓對應的權限
                mode |= amode;
            else
                mode &= ~amode;
            //調用chmod修改文件權限    
            if (chmod(path, mode) >= 0) {
                rv = JNI_TRUE;
            }
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

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

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        int mode;
        if (statMode(path, &mode)) {
            //設置成可讀的,則其他所有用戶都不可寫
            if (chmod(path, mode & ~(S_IWUSR | S_IWGRP | S_IWOTH)) >= 0) {
                rv = JNI_TRUE;
            }
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

static jboolean
statMode(const char *path, int *mode)
{
    struct stat64 sb;
    if (stat64(path, &sb) == 0) {
        *mode = sb.st_mode;
        return JNI_TRUE;
    }
    return JNI_FALSE;
}

3、getTotalSpace / getFreeSpace / getUsableSpace

      這幾個方法用於獲取當前文件所在磁盤分區的可用空間,已用空間和總的空間,單位字節,其實現如下:

public long getTotalSpace() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //檢查訪問權限
            sm.checkPermission(new RuntimePermission("getFileSystemAttributes"));
            sm.checkRead(path);
        }
        //檢查路徑合法
        if (isInvalid()) {
            return 0L;
        }
        //本地方法實現
        return fs.getSpace(this, FileSystem.SPACE_TOTAL);
    }

public long getFreeSpace() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("getFileSystemAttributes"));
            sm.checkRead(path);
        }
        if (isInvalid()) {
            return 0L;
        }
        return fs.getSpace(this, FileSystem.SPACE_FREE);
    }

public long getUsableSpace() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("getFileSystemAttributes"));
            sm.checkRead(path);
        }
        if (isInvalid()) {
            return 0L;
        }
        return fs.getSpace(this, FileSystem.SPACE_USABLE);
    }

其中本地方法實現都在UnixFileSystem_md.c中,其核心是用於獲取磁盤使用情況的statvfs64函數,其實現如下:

JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getSpace(JNIEnv *env, jobject this,
                                     jobject file, jint t)
{
    jlong rv = 0L;

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        struct statvfs64 fsstat;
        //將指定的內存塊的值初始化成0
        memset(&fsstat, 0, sizeof(fsstat));
        //調用statvfs64讀取磁盤使用情況
        if (statvfs64(path, &fsstat) == 0) {
            switch(t) {
            case java_io_FileSystem_SPACE_TOTAL:
                //磁盤塊的大小乘以總的可用磁盤塊個數得到總的可用空間,單位字節
                rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
                               long_to_jlong(fsstat.f_blocks));
                break;
            case java_io_FileSystem_SPACE_FREE:
                rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
                               long_to_jlong(fsstat.f_bfree));
                break;
            case java_io_FileSystem_SPACE_USABLE:
                rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
                               long_to_jlong(fsstat.f_bavail));
                break;
            default:
                assert(0);
            }
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

 測試用例如下:

@Test
    public void test7() throws Exception {
        //windows下getUsableSpace和getFreeSpace的返回值是一樣的
        File file=new File("D:\\test2.txt");
        System.out.println("getUsableSpace:"+file.getUsableSpace());
        System.out.println("getTotalSpace:"+file.getTotalSpace());
        System.out.println("getFreeSpace:"+file.getFreeSpace());
    }

4、compareTo / equals / hashCode

//用於比較兩個文件,Unix下通過文件名來比較
public int compareTo(File pathname) {
        return fs.compare(this, pathname);
    }

//基於compareTo方法判斷兩個文件是否一致
public boolean equals(Object obj) {
        if ((obj != null) && (obj instanceof File)) {
            return compareTo((File)obj) == 0;
        }
        return false;
    }

//Unix下基於文件名來計算hash值
public int hashCode() {
        return fs.hashCode(this);
    }

//UnixFileSystem的實現
public int compare(File f1, File f2) {
        return f1.getPath().compareTo(f2.getPath());
    }

public int hashCode(File f) {
        return f.getPath().hashCode() ^ 1234321;
    }

5、ExpiringCache

     ExpiringCache是io包下包內可見的一個基於LinkedHashMap實現的支持自動過期刪除的緩存實現,其包含的屬性如下:

    //元素的過期時間
    private long millisUntilExpiration;
    //保存元素的Map
    private Map<String,Entry> map;
    //queryCount表示讀寫計數,get和put時都會加1,如果超過queryOverflow則會清除掉所有的過期Entry
    private int queryCount;
    private int queryOverflow = 300;
    //最大元素個數
    private int MAX_ENTRIES = 200;

其中Entry是一個內部靜態類,其實現如下:

其構造方法實現如下:

重寫了removeEldestEntry方法,該方法是在新插入一個元素時調用的,如果返回true,則會將LinkHashMap中維護的雙向鏈表的鏈表頭節點對應的key從Map中移除。因爲是採用默認的構造函數,即雙向鏈表中維護的是元素插入順序而非訪問順序,所以當元素個數超過200時會移除第一個插入的元素,LinkHashMap的實現可以參考《java8 LinkedHashMap接口實現源碼解析》。

其核心就是插入鍵值對的put方法和根據key值獲取value的get方法,實現如下:

synchronized void put(String key, String val) {
        if (++queryCount >= queryOverflow) {
            //queryCount加1後,如果超過queryOverflow,則清理掉所有所有過期Entry
            cleanup();
        }
        //判斷是否存在未過期的相同key的Entry
        Entry entry = entryFor(key);
        if (entry != null) {
            //如果存在則更新修改時間
            entry.setTimestamp(System.currentTimeMillis());
            entry.setVal(val);
        } else {
            //不存在則插入一個新的
            map.put(key, new Entry(System.currentTimeMillis(), val));
        }
    }

synchronized String get(String key) {
        if (++queryCount >= queryOverflow) {
            //queryCount加1後,如果超過queryOverflow,則清理掉所有所有過期Entry
            cleanup();
        }
        //查找未過期的Entry
        Entry entry = entryFor(key);
        if (entry != null) {
           //如果存在則返回val
            return entry.val();
        }
        return null;
    }

private Entry entryFor(String key) {
        Entry entry = map.get(key);
        if (entry != null) {
            //如果不爲空則判斷其是否過期
            long delta = System.currentTimeMillis() - entry.timestamp();
            if (delta < 0 || delta >= millisUntilExpiration) {
               //已過期則移除,返回null
                map.remove(key);
                entry = null;
            }
        }
        return entry;
    }

private void cleanup() {
        Set<String> keySet = map.keySet();
        //將keySet中的key拷貝到keys數組中,避免通過keySet避免時刪除過期Entry會報併發修改異常
        String[] keys = new String[keySet.size()];
        int i = 0;
        for (String key: keySet) {
            keys[i++] = key;
        }
        for (int j = 0; j < keys.length; j++) {
            //entryFor判斷key已過期則會自動移除
            entryFor(keys[j]);
        }
        queryCount = 0;
    }

6、DeleteOnExitHook 

     DeleteOnExitHook也是io包包內可見的基於JVM關閉回調鉤子方法實現的在JVM關閉時刪除指定文件的工具類,其實現如下:

class DeleteOnExitHook {
    //LinkedHashSet底層通過LinkHashMap保存元素,遍歷時元素順序是元素插入的順序
    private static LinkedHashSet<String> files = new LinkedHashSet<>();
    static {
        //註冊JVM關閉時的回調函數,也可通過Runtime.getRuntime().addShutdownHook方法添加
        sun.misc.SharedSecrets.getJavaLangAccess()
            .registerShutdownHook(2 /* Shutdown hook invocation order */,
                true /* register even if shutdown in progress */,
                new Runnable() {
                    public void run() {
                       runHooks();
                    }
                }
        );
    }

    private DeleteOnExitHook() {}

    //對外暴露的核心方法,添加需要刪除的文件
    static synchronized void add(String file) {
        if(files == null) {
            // DeleteOnExitHook is running. Too late to add a file
            throw new IllegalStateException("Shutdown in progress");
        }

        files.add(file);
    }

    //執行文件刪除的方法
    static void runHooks() {
        LinkedHashSet<String> theFiles;
        
        //加鎖,避免被重複刪除
        synchronized (DeleteOnExitHook.class) {
            theFiles = files;
            files = null;
        }

        ArrayList<String> toBeDeleted = new ArrayList<>(theFiles);

        //將List中元素的順序反過來,即原來最後一個插入的元素遍歷時是第一個
        Collections.reverse(toBeDeleted);
        for (String filename : toBeDeleted) {
            (new File(filename)).delete();
        }
    }
}

@SuppressWarnings({"rawtypes", "unchecked"})
public static void reverse(List<?> list) {
        int size = list.size();
        //REVERSE_THRESHOLD是一個常量值18,RandomAccess是一個表示支持隨機訪問的標記類接口
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            //mid表示List的中間位置
            for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else {
            // instead of using a raw type here, it's possible to capture
            // the wildcard but it will require a call to a supplementary
            // private method
            //ListIterator是繼承自Iterator,支持元素遍歷和修改
            ListIterator fwd = list.listIterator();
            //size是指定返回的第一個元素的位置,沒有指定,從第一個元素開始遍歷
            ListIterator rev = list.listIterator(size);
            //同上,從0開始遍歷到中間位置,交換兩邊的元素即可
            for (int i=0, mid=list.size()>>1; i<mid; i++) {
                //獲取下一個元素
                Object tmp = fwd.next();
                //previous返回size前一個元素,即最後一個元素
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }

@SuppressWarnings({"rawtypes", "unchecked"})
//將i和j對應的元素交換
public static void swap(List<?> list, int i, int j) {
        // instead of using a raw type here, it's possible to capture
        // the wildcard but it will require a call to a supplementary
        // private method
        final List l = list;
        //set方法返回替換前原來的值
        l.set(i, l.set(j, l.get(i)));
    }

ShutdownHook相關說明可以參考《Java關閉鉤子的應用 - Shutdown Hook》


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