咱們知道不一樣的操做系統有各自的文件系統,這些文件系統又存在不少差別,而Java 由於是跨平臺的,因此它必需要統一處理這些不一樣平臺文件系統之間的差別,才能往上提供統一的入口。java
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複製代碼
該方法用於建立文件,本地方法邏輯是,bash
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;
}複製代碼
該方法用於列出指定目錄下的全部文件和目錄,本地方法處理邏輯以下,併發
java/lang/String
類對象,並檢查不能爲NULL。*
或\*
。.
和..
。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;
}複製代碼
該方法用來建立目錄,本地方法很簡單,就是獲取 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;
}複製代碼
該方法用來設置文件或目錄的最後修改時間,本地方法是先獲取 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;
}複製代碼
該方法用於將指定文件設置成只讀。本地方法邏輯爲,函數
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;
}複製代碼
該方法用於刪除 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);
}
}複製代碼
該方法用於重命名文件,須要將標準路徑緩存和標準路徑前綴緩存都清掉,而後調用本地方法 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;
}複製代碼
該方法用於檢查指定路徑文件或目錄是否可讀,這裏主要是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;
}
}複製代碼
該方法用於獲取可用的文件系統的根文件對象的數組。邏輯以下,
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();
}複製代碼
該方法用於獲取文件空間大小,包括總空間大小、剩餘空間大小和可用空間大小,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);複製代碼
本地方法的邏輯是,
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;
}複製代碼
該方法用於獲取系統容許的最大文件名長度。在調用 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;
}複製代碼
該方法用於比較兩個 File 對象,其實就是直接比較路徑字符串。
public int compare(File f1, File f2) {
return f1.getPath().compareToIgnoreCase(f2.getPath());
}複製代碼
該方法用於獲取 File 對象的哈希值,獲取 File對象路徑,再將字符串變成小寫,再調用字符串的 hashCode 方法,最後與 1234321 進行異或運算,獲得的值即爲該文件的哈希值。
public int hashCode(File f) {
return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321;
}複製代碼
如下是廣告和相關閱讀
=============廣告時間===============
公衆號的菜單已分爲「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。
鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠購買。感謝各位朋友。
=========================
相關閱讀:
JDK不一樣操做系統的FileSystem(Windows)上篇
JDK不一樣操做系統的FileSystem(Windows)中篇
歡迎關注: