JDK不一樣操做系統的FileSystem(unix-like)下篇

前言

咱們知道不一樣的操做系統有各自的文件系統,這些文件系統又存在不少差別,而Java 由於是跨平臺的,因此它必需要統一處理這些不一樣平臺文件系統之間的差別,才能往上提供統一的入口。java

關於FileSystem類

JDK 裏面抽象出了一個 FileSystem 來表示文件系統,不一樣的操做系統經過繼承該類實現各自的文件系統,好比 Windows NT/2000 操做系統則爲 WinNTFileSystem,而 unix-like 操做系統爲 UnixFileSystem。數組

須要注意的一點是,WinNTFileSystem類 和 UnixFileSystem類並非在同一個 JDK 裏面,也就是說它們是分開的,你只能在 Windows 版本的 JDK 中找到 WinNTFileSystem,而在 unix-like 版本的 JDK 中找到 UnixFileSystem,一樣地,其餘操做系統也有本身的文件系統實現類。緩存

這裏分紅兩個系列分析 JDK 對兩種(Windows 和 unix-like )操做系統的文件系統的實現類,前面已經講了 Windows操做系統,對應爲 WinNTFileSystem 類。這裏接着講 unix-like 操做系統,對應爲 UnixFileSystem 類。篇幅所限,分爲上中下篇,此爲下篇。安全

繼承結構

--java.lang.Object
  --java.io.FileSystem
    --java.io.UnixFileSystem
複製代碼

類定義

class UnixFileSystem extends FileSystem
複製代碼

主要屬性

  • slash 表示斜槓符號。
  • colon 表示冒號符號。
  • javaHome 表示Java Home目錄。
  • cache 用於緩存標準路徑。
  • javaHomePrefixCache 用於緩存標準路徑前綴。
private final char slash;
    private final char colon;
    private final String javaHome;
    private ExpiringCache cache = new ExpiringCache();
    private ExpiringCache javaHomePrefixCache = new ExpiringCache();
複製代碼

主要方法

list方法

該方法用於列出指定目錄下的全部文件和目錄,本地方法處理邏輯以下,bash

  1. 獲取 java/lang/String類對象,並檢查不能爲NULL。
  2. 經過 opendir 函數打開指定路徑目錄,爲空的話則返回空。
  3. 開闢 sizeof(struct dirent64) + (PATH_MAX + 1) 大小的空間,PATH_MAX爲1024。
  4. 經過 NewObjectArray 開闢一個初始大小爲 maxlen 即16的字符串數組,而後使用循環不斷調用 readdir64_r 函數獲取目錄下的元素,並存放到字符串數組中。這個過程當中若是數組滿了,則按照原先數組大小成倍擴擴展,擴展後將原數組的值複製到新數組中。
  5. 完成後關閉目錄並釋放 dirent64。
  6. 執行完上述操做,表明目錄下的文件和目錄的字符串數組大小就已經肯定了,接着按照該大小再一次用 NewObjectArray 開闢一個字符串數組,並把原來數組的值複製過來,這就是最終的字符串數據了。
  7. 中途遇到錯誤則會跳轉到 error 標籤處,執行關閉目錄和釋放資源,並返回空。
private native boolean delete0(File f);

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;

    str_class = JNU_ClassString(env);
    CHECK_NULL_RETURN(str_class, NULL);

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        dir = opendir(path);
    } END_PLATFORM_STRING(env, path);
    if (dir == NULL) return NULL;

    ptr = malloc(sizeof(struct dirent64) + (PATH_MAX + 1));
    if (ptr == NULL) {
        JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
        closedir(dir);
        return NULL;
    }

    len = 0;
    maxlen = 16;
    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
    if (rv == NULL) goto error;

    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;
            (*env)->DeleteLocalRef(env, old);
        }
#ifdef MACOSX
        name = newStringPlatform(env, ptr->d_name);
#else
        name = JNU_NewStringPlatform(env, ptr->d_name);
#endif
        if (name == NULL) goto error;
        (*env)->SetObjectArrayElement(env, rv, len++, name);
        (*env)->DeleteLocalRef(env, name);
    }
    closedir(dir);
    free(ptr);

    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;
}
複製代碼

createDirectory方法

該方法用來建立目錄,本地方法很簡單,就是獲取 File 對象對應的路徑,再經過 mkdir 函數建立目錄。其中0777,表示文件全部者、文件全部者所在的組的用戶、其餘用戶,都有權限進行讀、寫、執行的操做。併發

public native boolean createDirectory(File f);

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) {
        if (mkdir(path, 0777) == 0) {
            rv = JNI_TRUE;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}
複製代碼

rename方法

該方法用於重命名文件,須要將標準路徑緩存和標準路徑前綴緩存都清掉,而後調用本地方法 rename0 執行重命名操做。機器學習

public boolean rename(File f1, File f2) {
        cache.clear();
        javaHomePrefixCache.clear();
        return rename0(f1, f2);
    }

private native boolean rename0(File f1, File f2);
複製代碼

本地方法主要調用了 rename 函數,根據 Java 層傳入的兩個 File 對象對應的路徑進行重命名。分佈式

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) {
            if (rename(fromPath, toPath) == 0) {
                rv = JNI_TRUE;
            }
        } END_PLATFORM_STRING(env, toPath);
    } END_PLATFORM_STRING(env, fromPath);
    return rv;
}
複製代碼

setLastModifiedTime方法

該方法用來設置文件或目錄的最後修改時間。本地方法是先獲取 File 對象對應的路徑,再用 stat64 函數獲取指定文件或目錄的屬性,接着經過 st_atime 成員獲得文件最後訪問時間,這個時間不用改,要改的是最後修改時間,因此根據 Java 層傳入的時間做爲最後修改時間,最後經過 utimes 函數設置文件的最後修改時間。函數

public native boolean setLastModifiedTime(File f, long time);

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;

        if (stat64(path, &sb) == 0) {
            struct timeval tv[2];

            tv[0].tv_sec = sb.st_atime;
            tv[0].tv_usec = 0;

            tv[1].tv_sec = time / 1000;
            tv[1].tv_usec = (time % 1000) * 1000;

            if (utimes(path, tv) == 0)
                rv = JNI_TRUE;
        }
    } END_PLATFORM_STRING(env, path);

    return rv;
}
複製代碼

setReadOnly方法

該方法用於將指定文件設置成只讀。本地方法邏輯是先經過 statMode 函數獲取文件的屬性,再去掉 S_IWUSR、S_IWGRP 和 S_IWOTH 標識,分別表示用戶寫權限、用戶組用戶寫權限和非全部者和用戶組用戶寫權限。最後經過 chmod 函數完成文件只讀屬性設置。學習

public native boolean setReadOnly(File f);

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;
}
複製代碼

listRoots方法

該方法用於獲取可用的文件系統的根文件對象的數組,對於 unix-like 來講,也就是隻有一個根目錄了,這以前還會用安全管理器檢查下是否有根目錄的權限。

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];
        }
    }
複製代碼

getSpace方法

該方法用於獲取所掛載的文件系統(包含該方法指定的路徑)的空間大小,包括總空間大小、空閒空間大小和可用空間大小,Java 層分別用 SPACE_TOTAL = 0 SPACE_FREE = 1 SPACE_USABLE = 2標識。要查詢某個文件的根目錄的某某空間大小則將對應的標識傳入,經過 getSpace0 本地方法得到。

能夠看到本地方法使用了 statfs 或 statvfs64 函數來獲取文件系統的信息,進而經過塊數量乘以塊大小獲得總空間大小、空閒空間大小和可用空間大小。

至於爲何分別用 statfs 或 statvfs64 函數,其中 statfs 函數屬於特定系統的,而 statvfs64 函數符合 POSIX 標準。通常最好優先使用 statvfs,之前的 JDK(1.7) 實現也是經過statvfs,而這裏用 #ifdef MACOSX進行判斷,應該是由於 MACOSX 系統對 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) {
#ifdef MACOSX
        struct statfs fsstat;
#else
        struct statvfs64 fsstat;
#endif
        memset(&fsstat, 0, sizeof(fsstat));
#ifdef MACOSX
        if (statfs(path, &fsstat) == 0) {
            switch(t) {
                case java_io_FileSystem_SPACE_TOTAL:
                    rv = jlong_mul(long_to_jlong(fsstat.f_bsize),
                                   long_to_jlong(fsstat.f_blocks));
                    break;
                case java_io_FileSystem_SPACE_FREE:
                    rv = jlong_mul(long_to_jlong(fsstat.f_bsize),
                                   long_to_jlong(fsstat.f_bfree));
                    break;
                case java_io_FileSystem_SPACE_USABLE:
                    rv = jlong_mul(long_to_jlong(fsstat.f_bsize),
                                   long_to_jlong(fsstat.f_bavail));
                    break;
                default:
                    assert(0);
            }
        }
#else
        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);
            }
        }
#endif
    } END_PLATFORM_STRING(env, path);
    return rv;
}
複製代碼

getNameMax方法

該方法用於獲取系統容許的最大文件名長度,直接經過 getNameMax0 本地獲取最大長度,而後判斷不能超過整型的最大值。

public int getNameMax(String path) {
        long nameMax = getNameMax0(path);
        if (nameMax > Integer.MAX_VALUE) {
            nameMax = Integer.MAX_VALUE;
        }
        return (int)nameMax;
    }
複製代碼

本地方法經過 pathconf 函數來獲取指定路徑下的文件名長度限制值,傳入 _PC_NAME_MAX 便可獲得。

JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getNameMax0(JNIEnv *env, jobject this,
                                        jstring pathname)
{
    jlong length = -1;
    WITH_PLATFORM_STRING(env, pathname, path) {
        length = (jlong)pathconf(path, _PC_NAME_MAX);
    } END_PLATFORM_STRING(env, path);
    return length != -1 ? length : (jlong)NAME_MAX;
}
複製代碼

compare方法

該方法用於比較兩個 File 對象,其實就是直接比較路徑字符串。

public int compare(File f1, File f2) {
        return f1.getPath().compareTo(f2.getPath());
    }
複製代碼

hashCode方法

該方法用於獲取 File 對象的哈希值,獲取 File對象路徑,再將字符串變成小寫,再調用字符串的 hashCode 方法,最後與 1234321 進行異或運算,獲得的值即爲該文件的哈希值。

public int hashCode(File f) {
        return f.getPath().hashCode() ^ 1234321;
    }
複製代碼

=============廣告時間===============

公衆號的菜單已分爲「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。

鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠購買。感謝各位朋友。

爲何寫《Tomcat內核設計剖析》

=========================

相關閱讀:

JDK不一樣操做系統的FileSystem(Windows)上篇

JDK不一樣操做系統的FileSystem(Windows)中篇

JDK不一樣操做系統的FileSystem(Windows)下篇

JDK不一樣操做系統的FileSystem(unix-like)上篇

JDK不一樣操做系統的FileSystem(unix-like)中篇

歡迎關注:

這裏寫圖片描述
相關文章
相關標籤/搜索