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,一樣地,其餘操做系統也有本身的文件系統實現類。bash

這裏分紅兩個系列分析 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();
複製代碼

主要方法

parentOrNull 方法

該方法用於獲取路徑的父目錄,它的思路其實很簡單,就是從路徑的最後一個字符開始向前尋找路徑分隔符(斜槓),正常狀況下,找到第一個路徑分隔符的位置,再從頭開始截取到該位置便可。但可能存在...的狀況,因此若是連續遇到兩個.則返回 null;若是路徑以.結尾也是返回 null;若是路徑沒有父目錄或以分隔符結尾都返回null。機器學習

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 {
                ++nonDotCount;
                adjacentDots = 0;
            }
            --idx;
        }
        return null;
    }
複製代碼

getBooleanAttributes方法

這是一個本地方法,它主要的做用是能夠用來判斷 File 對象對應的文件或目錄是否存在,判斷 File 對象對應的是否是文件,判斷 File 對象對應的是否是目錄,判斷 File 對象是否是隱藏文件或目錄。分佈式

從實現能夠看到是經過 getBooleanAttributes0 本地方法獲取「是否存在」、「是否爲文件」和「是否爲目錄」三個屬性值,但「是否爲隱藏」則沒法從本地方法獲取到,而是經過判斷文件名是否以.開頭來判斷是否隱藏,由於在 unix-like 中約定以此開頭的都是隱藏文件或目錄。函數

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

但這裏爲何返回的是一個 int 類型呢?由於這裏爲了高效利用數據,用位做爲不一樣屬性的標識,分別爲 0x0一、0x0二、0x0四、0x08,分別表明是否存在、是否爲文件、是否爲目錄和是否爲隱藏文件或目錄。學習

public native int getBooleanAttributes0(File f);
複製代碼

本地方法的主要邏輯是經過 stat64 函數獲取文件屬性,在經過或運算將屬性信息裝進整型數值中。ui

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)) {
            int fmt = mode & S_IFMT;
            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;
}

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

checkAccess方法

這是一個本地方法,它主要的做用是判斷某個文件或目錄是否可讀、是否可寫、是否可執行。這裏一樣用位標識這些屬性,分別用0x0一、0x0二、0x04表示可執行、可寫、可讀。this

public native boolean checkAccess(File f, int access);
複製代碼

本地方法的實現主要經過 access 函數,能夠看到可讀、可寫和可執行三種狀況對應的mode分別爲R_OKW_OKX_OK。擁有對應權限則返回0,不然返回-1。最後往Java層返回一個 boolean 值。

JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_checkAccess(JNIEnv *env, jobject this,
                                        jobject file, jint a)
{
    jboolean rv = JNI_FALSE;
    int mode = 0;
    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) {
        if (access(path, mode) == 0) {
            rv = JNI_TRUE;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}
複製代碼

getLastModifiedTime方法

該方法用於獲取文件或目錄的最後修改時間,本地方法邏輯是經過 stat64 函數獲取對應路徑文件屬性,而後獲取結構體的 st_mtime 成員便可。

public native long getLastModifiedTime(File f);
 
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;
}
複製代碼

這裏爲何能經過 ids 直接獲取到路徑呢?且看下面,其實在 Java 層的 UnixFileSystem 類被加載時就會執行一個代碼塊,initIDs 方法會將 File 類的 path 屬性關聯到 ids 中,進而能夠根據該屬性獲取到路徑。

private static native void initIDs();

    static {
        initIDs();
    }
複製代碼
static struct {
    jfieldID path;
} ids;


JNIEXPORT void JNICALL
Java_java_io_UnixFileSystem_initIDs(JNIEnv *env, jclass cls)
{
    jclass fileClass = (*env)->FindClass(env, "java/io/File");
    if (!fileClass) return;
    ids.path = (*env)->GetFieldID(env, fileClass,
                                  "path", "Ljava/lang/String;");
}
複製代碼

getLength方法

該方法用於獲取文件或目錄的長度,本地方法邏輯是經過 stat64 函數獲取對應路徑文件屬性,其中路徑一樣是經過 ids 得到,而後獲取結構體的 st_size 成員便可。

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

setPermission方法

該方法主要用於設置 File 對象的訪問權限,核心邏輯是經過 chmod 函數來實現,具體邏輯以下:

  1. 對於 owneronly 變量,表示是否僅修改文件全部者的權限。
  2. 若是設爲 ACCESS_READ,則根據 owneronly 變量,將 chmod 函數對應的參數值設爲 S_IRUSR 或 S_IRUSR | S_IRGRP | S_IROTH。
  3. 若是設爲 ACCESS_WRITE,則根據 owneronly 變量,將 chmod 函數對應的參數值設爲 S_IWUSR 或 S_IWUSR | S_IWGRP | S_IWOTH。
  4. 若是設爲 ACCESS_EXECUTE,則根據 owneronly 變量,將 chmod 函數對應的參數值設爲 S_IXUSR 或 S_IXUSR | S_IXGRP | S_IXOTH。
  5. 經過 stat64 函數獲取路徑對應文件的屬性,並根據 enable 變量來賦予新值,最後經過 chmod 函數完成權限的修改。
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;
        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);
        }
        if (statMode(path, &mode)) {
            if (enable)
                mode |= amode;
            else
                mode &= ~amode;
            if (chmod(path, mode) >= 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;
}
複製代碼

createFileExclusively方法

該方法用於建立文件,本地方法邏輯是,

  1. 若是是根目錄則不作任何操做,由於根目錄確定存在的。
  2. 經過 handleOpen 函數完成建立的邏輯,該方法其實經過調用 open64 函數建立文件,其中傳入的參數爲 O_RDWR | O_CREAT | O_EXCL,表示以讀寫模式打開,而且若是文件不存在則建立,若是文件已存在則返回-1。
  3. 建立成功後經過 fstat64 函數獲取文件屬性,用 S_ISDIR 函數判斷若是是目錄則關閉文件而且設置 errno 爲 EISDIR。
  4. 若是 handleOpen 函數返回了-1,則若是 errno 不爲 EEXIST 則拋出錯誤,也就是說文件已經存在的話則不拋出錯誤。
  5. 最後建立文件成功後關閉它,返回成功。
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;
        if (strcmp (path, "/")) {
            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)
                    JNU_ThrowIOExceptionWithLastError(env, path);
                rv = JNI_TRUE;
            }
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

FD
handleOpen(const char *path, int oflag, int mode) {
    FD fd;
    RESTARTABLE(open64(path, oflag, mode), fd);
    if (fd != -1) {
        struct stat64 buf64;
        int result;
        RESTARTABLE(fstat64(fd, &buf64), result);
        if (result != -1) {
            if (S_ISDIR(buf64.st_mode)) {
                close(fd);
                errno = EISDIR;
                fd = -1;
            }
        } else {
            close(fd);
            fd = -1;
        }
    }
    return fd;
}
複製代碼

delete方法

該方法用於刪除 File 對象指定路徑,須要將標準路徑緩存和標準路徑前綴緩存都清掉,而後調用本地方法 delete0 執行刪除操做。

public boolean delete(File f) {
        cache.clear();
        javaHomePrefixCache.clear();
        return delete0(f);
    }
    
private native boolean delete0(File f);
複製代碼

本地方法實際上就是調用了 remove 函數對指定路徑文件進行刪除。

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

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

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

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

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

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

相關閱讀:

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

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

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

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

歡迎關注:

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