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

前言

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

關於FileSystem類

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

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

這裏分紅兩個系列分析 JDK 對兩種(Windows 和Linux)操做系統的文件系統的實現類,先講 Windows操做系統,對應爲 WinNTFileSystem 類。 因爲篇幅較長,《JDK不一樣操做系統的FileSystem(Windows)》分爲上中下篇,此爲下篇。安全

繼承結構

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

createFileExclusively方法

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

  1. 將路徑轉成寬字符形式。
  2. 判斷路徑是否爲系統保留設備名。
  3. 調用 CreateFileW 函數建立文件,使用了 CREATE_NEW 模式,僅僅在不存在該文件時才建立。
  4. 若是已經存在該文件,儘可能不拋出異常,而是返回 false,此過程還會嘗試讀取該文件的屬性,失敗則拋IO異常。
public native boolean createFileExclusively(String path)
            throws IOException;

JNIEXPORT jboolean JNICALL
Java_java_io_WinNTFileSystem_createFileExclusively(JNIEnv *env, jclass cls,
                                                   jstring path)
{
    HANDLE h = NULL;
    WCHAR *pathbuf = pathToNTPath(env, path, JNI_FALSE);
    if (pathbuf == NULL)
        return JNI_FALSE;
    if (isReservedDeviceNameW(pathbuf)) {
        free(pathbuf);
        return JNI_FALSE;
    }
    h = CreateFileW(
        pathbuf,                             
        GENERIC_READ | GENERIC_WRITE,        
        FILE_SHARE_READ | FILE_SHARE_WRITE,   
        NULL,                                 
        CREATE_NEW,                           
        FILE_ATTRIBUTE_NORMAL |
            FILE_FLAG_OPEN_REPARSE_POINT,    
        NULL);

    if (h == INVALID_HANDLE_VALUE) {
        DWORD error = GetLastError();
        if ((error != ERROR_FILE_EXISTS) && (error != ERROR_ALREADY_EXISTS)) {
            DWORD a = GetFileAttributesW(pathbuf);
            if (a == INVALID_FILE_ATTRIBUTES) {
                SetLastError(error);
                JNU_ThrowIOExceptionWithLastError(env, "Could not open file");
            }
        }
        free(pathbuf);
        return JNI_FALSE;
    }
    free(pathbuf);
    CloseHandle(h);
    return JNI_TRUE;
}複製代碼

list方法

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

  1. 獲取 java/lang/String類對象,並檢查不能爲NULL。
  2. 獲取 File 對象對應的路徑。
  3. 按照路徑長度從新分配空間並將路徑拷貝到 search_path。
  4. 經過 GetFileAttributesW 函數獲取指定路徑的文件屬性,若是獲得 INVALID_FILE_ATTRIBUTES 或若是爲目錄則直接返回NULL。
  5. 去除尾部多餘的空格。
  6. 在路徑的尾部添加*\*
  7. 經過 FindFirstFileW 函數獲取到第一個文件。
  8. 接着經過 while 循環和 FindNextFileW 函數不斷獲取下一個文件,並將獲得的文件名放到字符串數組中,並且文件名不能爲...
  9. 返回文件名數組,其中能夠看到文件名數組的初始長度爲16,若是超過該長度後則按照原來長度的兩倍從新建立字符串數組對象,再將原數組複製到新數組中。
public native String[] list(File f);

JNIEXPORT jobjectArray JNICALL
Java_java_io_WinNTFileSystem_list(JNIEnv *env, jobject this, jobject file)
{
    WCHAR *search_path;
    HANDLE handle;
    WIN32_FIND_DATAW find_data;
    int len, maxlen;
    jobjectArray rv, old;
    DWORD fattr;
    jstring name;
    jclass str_class;
    WCHAR *pathbuf;

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

    pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL)
        return NULL;
    search_path = (WCHAR*)malloc(2*wcslen(pathbuf) + 6);
    if (search_path == 0) {
        free (pathbuf);
        errno = ENOMEM;
        JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
        return NULL;
    }
    wcscpy(search_path, pathbuf);
    free(pathbuf);
    fattr = GetFileAttributesW(search_path);
    if (fattr == INVALID_FILE_ATTRIBUTES) {
        free(search_path);
        return NULL;
    } else if ((fattr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
        free(search_path);
        return NULL;
    }

    len = (int)wcslen(search_path);
    while (search_path[len-1] == L' ') {
        len--;
    }
    search_path[len] = 0;

    if ((search_path[0] == L'\\' && search_path[1] == L'\0') ||
        (search_path[1] == L':'
        && (search_path[2] == L'\0'
        || (search_path[2] == L'\\' && search_path[3] == L'\0')))) {
        wcscat(search_path, L"*");
    } else {
        wcscat(search_path, L"\\*");
    }

    handle = FindFirstFileW(search_path, &find_data);
    free(search_path);
    if (handle == INVALID_HANDLE_VALUE) {
        if (GetLastError() != ERROR_FILE_NOT_FOUND) {
            return NULL;
        } else {
            rv = (*env)->NewObjectArray(env, 0, str_class, NULL);
            return rv;
        }
    }

    len = 0;
    maxlen = 16;
    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
    if (rv == NULL) 
        return NULL;
    do {
        if (!wcscmp(find_data.cFileName, L".")
                                || !wcscmp(find_data.cFileName, L".."))
           continue;
        name = (*env)->NewString(env, find_data.cFileName,
                                 (jsize)wcslen(find_data.cFileName));
        if (name == NULL)
            return NULL; 
        if (len == maxlen) {
            old = rv;
            rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
            if (rv == NULL || JNU_CopyObjectArray(env, rv, old, len) < 0)
                return NULL; 
            (*env)->DeleteLocalRef(env, old);
        }
        (*env)->SetObjectArrayElement(env, rv, len++, name);
        (*env)->DeleteLocalRef(env, name);

    } while (FindNextFileW(handle, &find_data));

    if (GetLastError() != ERROR_NO_MORE_FILES)
        return NULL; 
    FindClose(handle);

    if (len < maxlen) {
        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;
}複製代碼

createDirectory方法

該方法用來建立目錄,本地方法很簡單,就是獲取 File 對象對應的路徑,再調用 CreateDirectoryW 函數建立目錄。機器學習

public native boolean createDirectory(File f);

JNIEXPORT jboolean JNICALL
Java_java_io_WinNTFileSystem_createDirectory(JNIEnv *env, jobject this,
                                             jobject file)
{
    BOOL h = FALSE;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL) {
        return JNI_FALSE;
    }
    h = CreateDirectoryW(pathbuf, NULL);
    free(pathbuf);

    if (h == 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}複製代碼

setLastModifiedTime方法

該方法用來設置文件或目錄的最後修改時間,本地方法是先獲取 File 對象對應的路徑,再用 CreateFileW 函數打開指定文件或目錄,最後用 SetFileTime 函數設置最後修改時間。分佈式

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

JNIEXPORT jboolean JNICALL
Java_java_io_WinNTFileSystem_setLastModifiedTime(JNIEnv *env, jobject this,
                                                 jobject file, jlong time)
{
    jboolean rv = JNI_FALSE;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    HANDLE h;
    if (pathbuf == NULL)
        return JNI_FALSE;
    h = CreateFileW(pathbuf,
                    FILE_WRITE_ATTRIBUTES,
                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    FILE_FLAG_BACKUP_SEMANTICS,
                    0);
    if (h != INVALID_HANDLE_VALUE) {
        LARGE_INTEGER modTime;
        FILETIME t;
        modTime.QuadPart = (time + 11644473600000L) * 10000L;
        t.dwLowDateTime = (DWORD)modTime.LowPart;
        t.dwHighDateTime = (DWORD)modTime.HighPart;
        if (SetFileTime(h, NULL, NULL, &t)) {
            rv = JNI_TRUE;
        }
        CloseHandle(h);
    }
    free(pathbuf);

    return rv;
}複製代碼

setReadOnly方法

該方法用於將指定文件設置成只讀。本地方法邏輯爲,函數

  1. 獲取 File 對象對應的路徑。
  2. 經過 GetFileAttributesW 函數獲取文件屬性。
  3. 若是文件屬於超連接或者快捷方式,則先獲取對應的最終路徑,而後再用 GetFileAttributesW 函數獲取文件屬性。
  4. 判斷不爲目錄的話則經過 SetFileAttributesW 函數設置文件爲只讀,對應標識爲 FILE_ATTRIBUTE_READONLY。
public native boolean setReadOnly(File f);

JNIEXPORT jboolean JNICALL
Java_java_io_WinNTFileSystem_setReadOnly(JNIEnv *env, jobject this,
                                         jobject file)
{
    jboolean rv = JNI_FALSE;
    DWORD a;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL)
        return JNI_FALSE;
    a = GetFileAttributesW(pathbuf);

    if ((a != INVALID_FILE_ATTRIBUTES) &&
        ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0))
    {
        WCHAR *fp = getFinalPath(env, pathbuf);
        if (fp == NULL) {
            a = INVALID_FILE_ATTRIBUTES;
        } else {
            free(pathbuf);
            pathbuf = fp;
            a = GetFileAttributesW(pathbuf);
        }
    }

    if ((a != INVALID_FILE_ATTRIBUTES) &&
        ((a & FILE_ATTRIBUTE_DIRECTORY) == 0)) {
        if (SetFileAttributesW(pathbuf, a | FILE_ATTRIBUTE_READONLY))
            rv = JNI_TRUE;
    }
    free(pathbuf);
    return rv;
}複製代碼

delete方法

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

public boolean delete(File f) {
        cache.clear();
        prefixCache.clear();
        return delete0(f);
    }

private native boolean delete0(File f);複製代碼

本地方法先獲取 File 對象對應的路徑,而後再調用 removeFileOrDirectory 函數刪除目錄或文件。而 removeFileOrDirectory 函數的邏輯是先將指定路徑文件或目錄設置成 FILE_ATTRIBUTE_NORMAL,而後再用 GetFileAttributesW 函數獲取文件屬性,最後若是指定路徑爲目錄則調用 RemoveDirectoryW 函數刪除目錄,若是是文件則調用 DeleteFileW 函數刪除文件。

JNIEXPORT jboolean JNICALL
Java_java_io_WinNTFileSystem_delete0(JNIEnv *env, jobject this, jobject file)
{
    jboolean rv = JNI_FALSE;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL) {
        return JNI_FALSE;
    }
    if (removeFileOrDirectory(pathbuf) == 0) {
        rv = JNI_TRUE;
    }
    free(pathbuf);
    return rv;
}

static int
removeFileOrDirectory(const jchar *path)
{
    DWORD a;

    SetFileAttributesW(path, FILE_ATTRIBUTE_NORMAL);
    a = GetFileAttributesW(path);
    if (a == INVALID_FILE_ATTRIBUTES) {
        return 1;
    } else if (a & FILE_ATTRIBUTE_DIRECTORY) {
        return !RemoveDirectoryW(path);
    } else {
        return !DeleteFileW(path);
    }
}複製代碼

rename方法

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

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

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

本地方法分別先獲取原來的文件路徑和重命名的文件路徑,再經過 _wrename 函數進行重命名操做。

JNIEXPORT jboolean JNICALL
Java_java_io_WinNTFileSystem_rename0(JNIEnv *env, jobject this, jobject from,
                                     jobject to)
{

    jboolean rv = JNI_FALSE;
    WCHAR *frompath = fileToNTPath(env, from, ids.path);
    WCHAR *topath = fileToNTPath(env, to, ids.path);
    if (frompath != NULL && topath != NULL && _wrename(frompath, topath) == 0) {
        rv = JNI_TRUE;
    }
    free(frompath);
    free(topath);
    return rv;
}複製代碼

access方法

該方法用於檢查指定路徑文件或目錄是否可讀,這裏主要是JVM層的權限檢查,因此用的是 SecurityManager 安全管理器來檢測。

private boolean access(String path) {
        try {
            SecurityManager security = System.getSecurityManager();
            if (security != null) security.checkRead(path);
            return true;
        } catch (SecurityException x) {
            return false;
        }
    }複製代碼

listRoots方法

該方法用於獲取可用的文件系統的根文件對象的數組。邏輯以下,

  1. 先經過 listRoots0 本地方法獲取全部根文件。
  2. listRoots0 方法很簡單,就是直接用 GetLogicalDrives 函數獲取到操做系統的邏輯驅動器字符。須要注意的是 GetLogicalDrives 返回的是一個 int 類型,那麼它是怎麼表示驅動器字符的呢?其實也是經過位來標識,每一位對應表示一個邏輯驅動器是否存在,好比第一位若是是"1"則表示驅動器"A:"存在, 第二位若是是「1」則表示驅動器「B:」存在,以此類推。
  3. 獲得全部驅動器字符後經過一個for循環遍歷檢測驅動器的訪問權限,去掉無權限的驅動器,並統計一個有n個驅動器,這裏只須要循環26次,由於最多就是26個大寫字母。
  4. 實例化一個 File 數組,大小爲n。
  5. 再次經過一個26次的循環遍歷獲得有權限的驅動器,根據此驅動器符號實例化一個 File 對象,添加到 File 數組中。
  6. 返回 File 數組。
public File[] listRoots() {
        int ds = listRoots0();
        int n = 0;
        for (int i = 0; i < 26; i++) {
            if (((ds >> i) & 1) != 0) {
                if (!access((char)('A' + i) + ":" + slash))
                    ds &= ~(1 << i);
                else
                    n++;
            }
        }
        File[] fs = new File[n];
        int j = 0;
        char slash = this.slash;
        for (int i = 0; i < 26; i++) {
            if (((ds >> i) & 1) != 0)
                fs[j++] = new File((char)('A' + i) + ":" + slash);
        }
        return fs;
    }

private static native int listRoots0();

JNIEXPORT jint JNICALL
Java_java_io_WinNTFileSystem_listRoots0(JNIEnv *env, jclass ignored)
{
    return GetLogicalDrives();
}複製代碼

getSpace方法

該方法用於獲取文件空間大小,包括總空間大小、剩餘空間大小和可用空間大小,Java 層分別用 SPACE_TOTAL = 0 SPACE_FREE = 1 SPACE_USABLE = 2標識。要查詢某個文件的根目錄的某某空間大小則將對應的標識傳入,經過 getSpace0 本地方法得到。

public long getSpace(File f, int t) {
        if (f.exists()) {
            return getSpace0(f, t);
        }
        return 0;
    }

private native long getSpace0(File f, int t);複製代碼

本地方法的邏輯是,

  1. 獲取 File 對象對應的文件或目錄路徑。
  2. 經過 GetVolumePathNameW 函數獲取指定路徑對應的根路徑。
  3. 經過 GetDiskFreeSpaceExW 函數將總空間大小、空閒空間大小和可用空間大小獲取到。
  4. 根據傳入的標識返回不一樣的指標,好比 SPACE_TOTAL則返回總空間大小,其餘相似。
JNIEXPORT jlong JNICALL
Java_java_io_WinNTFileSystem_getSpace0(JNIEnv *env, jobject this,
                                       jobject file, jint t)
{
    WCHAR volname[MAX_PATH_LENGTH + 1];
    jlong rv = 0L;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);

    if (GetVolumePathNameW(pathbuf, volname, MAX_PATH_LENGTH)) {
        ULARGE_INTEGER totalSpace, freeSpace, usableSpace;
        if (GetDiskFreeSpaceExW(volname, &usableSpace, &totalSpace, &freeSpace)) {
            switch(t) {
            case java_io_FileSystem_SPACE_TOTAL:
                rv = long_to_jlong(totalSpace.QuadPart);
                break;
            case java_io_FileSystem_SPACE_FREE:
                rv = long_to_jlong(freeSpace.QuadPart);
                break;
            case java_io_FileSystem_SPACE_USABLE:
                rv = long_to_jlong(usableSpace.QuadPart);
                break;
            default:
                assert(0);
            }
        }
    }

    free(pathbuf);
    return rv;
}複製代碼

getNameMax方法

該方法用於獲取系統容許的最大文件名長度。在調用 getNameMax0 本地方法前會先作一些處理,若是路徑時絕對路徑,則獲取根路徑並加上 \\

public int getNameMax(String path) {
        String s = null;
        if (path != null) {
            File f = new File(path);
            if (f.isAbsolute()) {
                Path root = f.toPath().getRoot();
                if (root != null) {
                    s = root.toString();
                    if (!s.endsWith("\\")) {
                        s = s + "\\";
                    }
                }
            }
        }
        return getNameMax0(s);
    }

private native int getNameMax0(String path);複製代碼

本地方法中經過 GetVolumeInformationW 函數獲得系統容許的最大文件名長度 maxComponentLength

JNIEXPORT jint JNICALL
Java_java_io_WinNTFileSystem_getNameMax0(JNIEnv *env, jobject this,
                                         jstring pathname)
{
    BOOL res = 0;
    DWORD maxComponentLength;

    if (pathname == NULL) {
            res = GetVolumeInformationW(NULL,
                                        NULL,
                                        0,
                                        NULL,
                                        &maxComponentLength,
                                        NULL,
                                        NULL,
                                        0);
    } else {
        WITH_UNICODE_STRING(env, pathname, path) {
            res = GetVolumeInformationW(path,
                                        NULL,
                                        0,
                                        NULL,
                                        &maxComponentLength,
                                        NULL,
                                        NULL,
                                        0);
        } END_UNICODE_STRING(env, path);
    }

    if (res == 0) {
        JNU_ThrowIOExceptionWithLastError(env,
            "Could not get maximum component length");
    }

    return (jint)maxComponentLength;
}複製代碼

compare方法

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

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

hashCode方法

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

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

如下是廣告相關閱讀

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

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

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

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

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

相關閱讀:

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

歡迎關注:

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