咱們知道不一樣的操做系統有各自的文件系統,這些文件系統又存在不少差別,而Java 由於是跨平臺的,因此它必需要統一處理這些不一樣平臺文件系統之間的差別,才能往上提供統一的入口。html
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複製代碼
返回默認的父路徑,直接返回 slash 即\
。緩存
public String getDefaultParent() {
return ("" + slash);
}複製代碼
該方法主要是格式化路徑。主要邏輯是完成相似如下的轉換處理:bash
/c:/test
--> c:/test
。c:/test/
--> c:/test
,但要注意,c:/
--> c:/
,這是經過長度來限制的,即當長度超過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;
}複製代碼
判斷文件是不是絕對路徑,先獲取文件路徑頭部長度,若是長度爲3則爲絕對路徑,若是長度爲2且路徑第一個字符爲\
也爲絕對路徑。函數
public boolean isAbsolute(File f) {
int pl = f.getPrefixLength();
return (((pl == 2) && (f.getPath().charAt(0) == slash))
|| (pl == 3));
}複製代碼
該方法用來標準化一個路徑,標準路徑不只是一個絕對路徑並且仍是惟一的路徑,並且標準的定義是依賴於操做系統的,通常在標準化路徑時會先將路徑轉成絕對路徑,而後才根據操做系統解析成惟一形式的路徑。這個過程比較典型的就是處理包含"."或".."的路徑,還有符號連接和驅動盤符號大小寫等。ui
:
,此時路徑相似爲c:
,直接返回路徑或加上:
返回。:
,且路徑第三個字符爲\
,此時路徑相似爲c:\
,直接返回路徑或加上:
返回。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
\
和..
等符號,好比 "test"、"\test" 和"..\test",則會處理成"C:\Documents and Settings\user\My Documents\test"、"C:\test"和"C:\Documents and Settings\user\test"。.
。\\server\share\file_path
,則此時分別獲取 server 和 share,將其保存到結果字符數組中。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;
}複製代碼
這是一個本地方法,它主要的做用是能夠用來判斷 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;
}複製代碼
這是一個本地方法,它主要的做用是判斷某個文件或目錄是否可讀、是否可寫、是否可執行。這裏一樣用位標識這些屬性,分別用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;
}
}複製代碼
該方法用於獲取文件或目錄的最後修改時間,本地方法先獲取 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;
}複製代碼
該方法御用獲取文件或目錄的長度。邏輯爲,
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;
}複製代碼
該方法主要用於設置 File 對象的訪問權限。邏輯以下,
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… 進行預約。感謝各位朋友。
=========================
相關閱讀:
JDK不一樣操做系統的FileSystem(Windows)上篇
歡迎關注: