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

前言

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

關於FileSystem類

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

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

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

繼承結構

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

getDefaultParent方法

返回默認的父路徑,直接返回 slash 即\緩存

public String getDefaultParent() {
        return ("" + slash);
    }複製代碼

fromURIPath方法

該方法主要是格式化路徑。主要邏輯是完成相似如下的轉換處理:bash

  1. /c:/test --> c:/test
  2. c:/test/ --> c:/test,但要注意,c:/ --> c:/,這是經過長度來限制的,即當長度超過3時纔會去掉尾部的 /
  3. /test/ --> /test
public String fromURIPath(String path) {
        String p = path;
        if ((p.length() > 2) && (p.charAt(2) == ':')) {
            p = p.substring(1);
            if ((p.length() > 3) && p.endsWith("/"))
                p = p.substring(0, p.length() - 1);
        } else if ((p.length() > 1) && p.endsWith("/")) {
            p = p.substring(0, p.length() - 1);
        }
        return p;
    }複製代碼

isAbsolute方法

判斷文件是不是絕對路徑,先獲取文件路徑頭部長度,若是長度爲3則爲絕對路徑,若是長度爲2且路徑第一個字符爲\也爲絕對路徑。函數

public boolean isAbsolute(File f) {
        int pl = f.getPrefixLength();
        return (((pl == 2) && (f.getPath().charAt(0) == slash))
                || (pl == 3));
    }複製代碼

canonicalize方法

該方法用來標準化一個路徑,標準路徑不只是一個絕對路徑並且仍是惟一的路徑,並且標準的定義是依賴於操做系統的,通常在標準化路徑時會先將路徑轉成絕對路徑,而後才根據操做系統解析成惟一形式的路徑。這個過程比較典型的就是處理包含"."或".."的路徑,還有符號連接和驅動盤符號大小寫等。ui

  1. 若是路徑長度爲2,且路徑第一個字符爲字母,且路徑第二個字符爲:,此時路徑相似爲c:,直接返回路徑或加上:返回。
  2. 若是路徑長度爲3,且路徑第一個字符爲字母,且路徑第二個字符爲:,且路徑第三個字符爲\,此時路徑相似爲c:\,直接返回路徑或加上:返回。
  3. 若是不使用緩存則直接調用 canonicalize0 本地方法獲取標準化路徑。
  4. 若是使用了緩存則在緩存中查找,存在則直接返回,不然先調用 canonicalize0 本地方法獲取標準化路徑,再將路徑放進緩存中。
  5. 另外,還提供了前綴緩存可使用,它緩存了標準路徑的父目錄,這樣就能夠節省了前綴部分的處理,前綴緩存的邏輯也是第一次標準化後將其緩存起來,下次則可從前綴緩存中查詢。
  6. 使用前綴緩存來標準化路徑時是調用 canonicalizeWithPrefix 方法的,該方法最終也是調一個本地方法 canonicalizeWithPrefix0,這裏再也不列出該方法代碼,此本地方法與前面的 canonicalize0 方法的邏輯差很少,惟一不一樣的是它不用遍歷整個路徑從頭開始檢查路徑的有效性,而只須要檢查一次整個路徑的有效性,這也是存在前綴緩存的緣由,節省了一些工做。
public String canonicalize(String path) throws IOException {
        int len = path.length();
        if ((len == 2) &&
            (isLetter(path.charAt(0))) &&
            (path.charAt(1) == ':')) {
            char c = path.charAt(0);
            if ((c >= 'A') && (c <= 'Z'))
                return path;
            return "" + ((char) (c-32)) + ':';
        } else if ((len == 3) &&
                   (isLetter(path.charAt(0))) &&
                   (path.charAt(1) == ':') &&
                   (path.charAt(2) == '\\')) {
            char c = path.charAt(0);
            if ((c >= 'A') && (c <= 'Z'))
                return path;
            return "" + ((char) (c-32)) + ':' + '\\';
        }
        if (!useCanonCaches) {
            return canonicalize0(path);
        } else {
            String res = cache.get(path);
            if (res == null) {
                String dir = null;
                String resDir = null;
                if (useCanonPrefixCache) {
                    dir = parentOrNull(path);
                    if (dir != null) {
                        resDir = prefixCache.get(dir);
                        if (resDir != null) {
                            String filename = path.substring(1 + dir.length());
                            res = canonicalizeWithPrefix(resDir, filename);
                            cache.put(dir + File.separatorChar + filename, res);
                        }
                    }
                }
                if (res == null) {
                    res = canonicalize0(path);
                    cache.put(path, res);
                    if (useCanonPrefixCache && dir != null) {
                        resDir = parentOrNull(res);
                        if (resDir != null) {
                            File f = new File(res);
                            if (f.exists() && !f.isDirectory()) {
                                prefixCache.put(dir, resDir);
                            }
                        }
                    }
                }
            }
            return res;
        }
    }

private native String canonicalize0(String path)
            throws IOException;

private String canonicalizeWithPrefix(String canonicalPrefix,
            String filename) throws IOException
    {
        return canonicalizeWithPrefix0(canonicalPrefix,
                canonicalPrefix + File.separatorChar + filename);
    }


private native String canonicalizeWithPrefix0(String canonicalPrefix,
            String pathWithCanonicalPrefix)
            throws IOException;複製代碼

由於標準化的具體實現是依賴於操做系統的,因此這部分工做交由本地方法去作,主要邏輯以下,this

  • 標準路徑最大長度是 MAX_PATH_LENGTH 即1024個字符。
  • 獲取路徑長度而且經過 currentDirLength 函數獲取當前工做目錄長度,該函數代碼再也不貼出來,主要邏輯是判斷是否爲驅動盤相對路徑,若是是的話則根據驅動盤符號獲取對應驅動盤的工做目錄並返回該目錄長度。不然就根據C的API函數 _wgetcwd 獲取工做目錄並返回長度。路徑長度加上工做目錄長度則爲總長度。
  • 經過 malloc 分配空間,注意這裏是寬字符。
  • 最後經過 wcanonicalize 函數標準化路徑。代碼較長,再也不貼出,主要邏輯是,
  1. 檢查路徑是否包含通配符,是則標準化失敗。
  2. 經過 _wfullpath 函數獲取絕對路徑,這個函數他會處理 \..等符號,好比 "test"、"\test" 和"..\test",則會處理成"C:\Documents and Settings\user\My Documents\test"、"C:\test"和"C:\Documents and Settings\user\test"。
  3. 檢查路徑中是否包含了非法的 .
  4. 獲取標準化驅動盤符號,將其大寫,保存到結果字符數組中。
  5. 若是是 UNC 路徑的話,UNC路徑格式如\\server\share\file_path,則此時分別獲取 server 和 share,將其保存到結果字符數組中。
  6. 經過循環檢測剩餘路徑的有效性,主要是使用 FindFirstFileW 函數獲取路徑對應文件的屬性信息,根據返回值可判斷其有效性,有效地路徑則保存到結果字符數組中。
  7. 最終結果字符數組則爲標準化後的路徑。
JNIEXPORT jstring JNICALL
Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this,
                                           jstring pathname)
{
    jstring rv = NULL;
    WCHAR canonicalPath[MAX_PATH_LENGTH];

    WITH_UNICODE_STRING(env, pathname, path) {
        int len = (int)wcslen(path);
        len += currentDirLength(path, len);
        if (len  > MAX_PATH_LENGTH - 1) {
            WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR));
            if (cp != NULL) {
                if (wcanonicalize(path, cp, len) >= 0) {
                    rv = (*env)->NewString(env, cp, (jsize)wcslen(cp));
                }
                free(cp);
            }
        } else
        if (wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH) >= 0) {
            rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath));
        }
    } END_UNICODE_STRING(env, path);
    if (rv == NULL) {
        JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
    }
    return rv;
}複製代碼

getBooleanAttributes方法

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

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

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

本地方法的主要邏輯是先獲得文件的路徑,再判斷路徑是否爲 windows 保留設備名稱,它包括如下這些,

static WCHAR* ReservedDEviceNames[] = {
    L"CON", L"PRN", L"AUX", L"NUL",
    L"COM1", L"COM2", L"COM3", L"COM4", L"COM5", L"COM6", L"COM7", L"COM8", L"COM9",
    L"LPT1", L"LPT2", L"LPT3", L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", L"LPT9",
    L"CLOCK$"
};複製代碼

接着經過 GetFileAttributesExW 函數獲取文件屬性,若是文件屬於超連接或者快捷方式,則須要再經過 CreateFileW 函數與 GetFileInformationByHandle 函數組合獲取文件信息,其中 CreateFileW 函數要使用 OPEN_EXISTING 表示僅僅打開文件而不是建立。獲得文件屬性後經過位的或操做來標識是否存在、是否文件、是否目錄、是否隱藏。

此外有一個特例也須要處理,就是 pagefile.sys 文件,它比較特殊,主要用於虛擬內存,它是文件而不是目錄。

JNIEXPORT jint JNICALL
Java_java_io_WinNTFileSystem_getBooleanAttributes(JNIEnv *env, jobject this,
                                                  jobject file)
{

    jint rv = 0;
    jint pathlen;

#define SPECIALFILE_NAMELEN 12

    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    WIN32_FILE_ATTRIBUTE_DATA wfad;
    if (pathbuf == NULL)
        return rv;
    if (!isReservedDeviceNameW(pathbuf)) {
        if (GetFileAttributesExW(pathbuf, GetFileExInfoStandard, &wfad)) {
            DWORD a = getFinalAttributesIfReparsePoint(pathbuf, wfad.dwFileAttributes);
            if (a != INVALID_FILE_ATTRIBUTES) {
                rv = (java_io_FileSystem_BA_EXISTS
                    | ((a & FILE_ATTRIBUTE_DIRECTORY)
                        ? java_io_FileSystem_BA_DIRECTORY
                        : java_io_FileSystem_BA_REGULAR)
                    | ((a & FILE_ATTRIBUTE_HIDDEN)
                        ? java_io_FileSystem_BA_HIDDEN : 0));
            }
        } else { 
            if (GetLastError() == ERROR_SHARING_VIOLATION) {
                rv = java_io_FileSystem_BA_EXISTS;
                if ((pathlen = (jint)wcslen(pathbuf)) >= SPECIALFILE_NAMELEN &&
                    (_wcsicmp(pathbuf + pathlen - SPECIALFILE_NAMELEN,
                              L"pagefile.sys") == 0) ||
                    (_wcsicmp(pathbuf + pathlen - SPECIALFILE_NAMELEN,
                              L"hiberfil.sys") == 0))
                  rv |= java_io_FileSystem_BA_REGULAR;
            }
        }
    }
    free(pathbuf);
    return rv;
}複製代碼

checkAccess方法

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

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

本地方法的邏輯是先獲取文件或目錄路徑,接着經過 GetFileAttributesW 函數獲取文件屬性,若是文件屬於超連接或者快捷方式,則須要再經過 CreateFileW 函數與 GetFileInformationByHandle 函數組合獲取文件信息,其中 CreateFileW 函數要使用 OPEN_EXISTING 表示僅僅打開文件而不是建立。能夠看到若是爲 INVALID_FILE_ATTRIBUTES 則是獲取失敗了,此時任何權限都是沒有的。獲取成功則當判斷可讀性和可執行性時都返回true,但檢查可寫時則還要判斷是否爲目錄(目錄直接可寫),並且還要看文件屬性是否爲 FILE_ATTRIBUTE_READONLY,只讀的話則不可寫。

JNIEXPORT jboolean
JNICALL Java_java_io_WinNTFileSystem_checkAccess(JNIEnv *env, jobject this,
                                                 jobject file, jint access)
{
    DWORD attr;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL)
        return JNI_FALSE;
    attr = GetFileAttributesW(pathbuf);
    attr = getFinalAttributesIfReparsePoint(pathbuf, attr);
    free(pathbuf);
    if (attr == INVALID_FILE_ATTRIBUTES)
        return JNI_FALSE;
    switch (access) {
    case java_io_FileSystem_ACCESS_READ:
    case java_io_FileSystem_ACCESS_EXECUTE:
        return JNI_TRUE;
    case java_io_FileSystem_ACCESS_WRITE:
        if ((attr & FILE_ATTRIBUTE_DIRECTORY) ||
            (attr & FILE_ATTRIBUTE_READONLY) == 0)
            return JNI_TRUE;
        else
            return JNI_FALSE;
    default:
        assert(0);
        return JNI_FALSE;
    }
}複製代碼

getLastModifiedTime方法

該方法用於獲取文件或目錄的最後修改時間,本地方法先獲取 File 對象的路徑,再經過 CreateFileW 函數和 GetFileTime 函數獲取最後修改時間,其中 CreateFileW 函數要使用 OPEN_EXISTING 表示僅僅打開文件而不是建立。

public native long getLastModifiedTime(File f);

JNIEXPORT jlong JNICALL
Java_java_io_WinNTFileSystem_getLastModifiedTime(JNIEnv *env, jobject this,
                                                 jobject file)
{
    jlong rv = 0;
    LARGE_INTEGER modTime;
    FILETIME t;
    HANDLE h;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL)
        return rv;
    h = CreateFileW(pathbuf,
                    0,
                    FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    FILE_FLAG_BACKUP_SEMANTICS,
                    NULL);
    if (h != INVALID_HANDLE_VALUE) {
        if (GetFileTime(h, NULL, NULL, &t)) {
            modTime.LowPart = (DWORD) t.dwLowDateTime;
            modTime.HighPart = (LONG) t.dwHighDateTime;
            rv = modTime.QuadPart / 10000;
            rv -= 11644473600000;
        }
        CloseHandle(h);
    }
    free(pathbuf);
    return rv;
}複製代碼

getLength方法

該方法御用獲取文件或目錄的長度。邏輯爲,

  1. 獲取 File 對象的路徑。
  2. 經過 GetFileAttributesExW 函數獲取文件屬性,而後經過 nFileSizeHigh 和 nFileSizeLow 獲得文件的長度。
  3. 若是文件屬於超連接或者快捷方式,則須要再經過 CreateFileW 函數與 GetFileInformationByHandle 函數組合獲取文件信息,其中 CreateFileW 函數要使用 OPEN_EXISTING 表示僅僅打開文件而不是建立,而後經過 nFileSizeHigh 和 nFileSizeLow 獲得文件的長度。
  4. 若是另外一個進程在使用文件,則不能訪問該文件,並報錯 ERROR_SHARING_VIOLATION,此時嘗試用 _wstati64 函數獲取文件的長度。
public native long getLength(File f);

JNIEXPORT jlong JNICALL
Java_java_io_WinNTFileSystem_getLength(JNIEnv *env, jobject this, jobject file)
{
    jlong rv = 0;
    WIN32_FILE_ATTRIBUTE_DATA wfad;
    WCHAR *pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL)
        return rv;
    if (GetFileAttributesExW(pathbuf,
                             GetFileExInfoStandard,
                             &wfad)) {
        if ((wfad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
            rv = wfad.nFileSizeHigh * ((jlong)MAXDWORD + 1) + wfad.nFileSizeLow;
        } else {
            BY_HANDLE_FILE_INFORMATION finfo;
            if (getFileInformation(pathbuf, &finfo)) {
                rv = finfo.nFileSizeHigh * ((jlong)MAXDWORD + 1) +
                    finfo.nFileSizeLow;
            }
        }
    } else {
        if (GetLastError() == ERROR_SHARING_VIOLATION) {
            struct _stati64 sb;
            if (_wstati64(pathbuf, &sb) == 0) {
                rv = sb.st_size;
            }
        }
    }
    free(pathbuf);
    return rv;
}複製代碼

setPermission方法

該方法主要用於設置 File 對象的訪問權限。邏輯以下,

  1. 若是設置額權限爲 ACCESS_READ 或 ACCESS_EXECUTE,則直接返回傳入的參數enable。
  2. 獲取 File 對象的路徑。
  3. 經過 GetFileAttributesW 函數獲取文件屬性。
  4. 若是文件屬於超連接或者快捷方式,則先獲取對應的最終路徑,而後再用 GetFileAttributesW 函數獲取文件屬性。
  5. 判斷若是不是目錄的話則根據傳入的參數enable設置屬性,而且經過 SetFileAttributesW 函數設置文件的讀寫權限。
public native boolean setPermission(File f, int access, boolean enable,
            boolean owneronly);

JNIEXPORT jboolean JNICALL
Java_java_io_WinNTFileSystem_setPermission(JNIEnv *env, jobject this,
                                           jobject file,
                                           jint access,
                                           jboolean enable,
                                           jboolean owneronly)
{
    jboolean rv = JNI_FALSE;
    WCHAR *pathbuf;
    DWORD a;
    if (access == java_io_FileSystem_ACCESS_READ ||
        access == java_io_FileSystem_ACCESS_EXECUTE) {
        return enable;
    }
    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 (enable)
            a =  a & ~FILE_ATTRIBUTE_READONLY;
        else
            a =  a | FILE_ATTRIBUTE_READONLY;
        if (SetFileAttributesW(pathbuf, a))
            rv = JNI_TRUE;
    }
    free(pathbuf);
    return rv;
}複製代碼

如下是廣告相關閱讀

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

鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠到 item.jd.com/12185360.ht… 進行預約。感謝各位朋友。

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

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

相關閱讀:

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

歡迎關注:

這裏寫圖片描述
這裏寫圖片描述

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