前言
我們知道不同的作業系統有各自的檔案系統,這些檔案系統又存在很多差異,而Java 因為是跨平臺的,所以它必須要統一處理這些不同平臺檔案系統之間的差異,才能往上提供統一的入口。
關於FileSystem類
JDK 裡面抽象出了一個 FileSystem 來表示檔案系統,不同的作業系統通過繼承該類實現各自的檔案系統,比如 Windows NT/2000 作業系統則為 WinNTFileSystem,而 unix-like 作業系統為 UnixFileSystem。
需要注意的一點是,WinNTFileSystem類 和 UnixFileSystem類並不是在同一個 JDK 裡面,也就是說它們是分開的,你只能在 Windows 版本的 JDK 中找到 WinNTFileSystem,而在 unix-like 版本的 JDK 中找到 UnixFileSystem,同樣地,其他作業系統也有自己的檔案系統實現類。
這裡分成兩個系列分析 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 型別呢?因為這裡為了高效利用資料,用位作為不同屬性的標識,分別為
0x01、0x02、0x04、0x08
,分別代表是否存在、是否為檔案、是否為目錄和是否為隱藏檔案或目錄。
public native int getBooleanAttributes0(File f);
複製程式碼
本地方法的主要邏輯是通過 stat64 函式獲取檔案屬性,在通過或運算將屬性資訊裝進整型數值中。
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方法
這是一個本地方法,它主要的作用是判斷某個檔案或目錄是否可讀、是否可寫、是否可執行。這裡同樣用位標識這些屬性,分別用0x01、0x02、0x04
表示可執行、可寫、可讀。
public native boolean checkAccess(File f, int access);
複製程式碼
本地方法的實現主要通過 access 函式,可以看到可讀、可寫和可執行三種情況對應的mode分別為R_OK
、W_OK
、X_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 函式來實現,具體邏輯如下:
- 對於 owneronly 變數,表示是否僅修改檔案所有者的許可權。
- 如果設為
ACCESS_READ
,則根據 owneronly 變數,將 chmod 函式對應的引數值設為 S_IRUSR 或 S_IRUSR | S_IRGRP | S_IROTH。 - 如果設為
ACCESS_WRITE
,則根據 owneronly 變數,將 chmod 函式對應的引數值設為 S_IWUSR 或 S_IWUSR | S_IWGRP | S_IWOTH。 - 如果設為
ACCESS_EXECUTE
,則根據 owneronly 變數,將 chmod 函式對應的引數值設為 S_IXUSR 或 S_IXUSR | S_IXGRP | S_IXOTH。 - 通過 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方法
該方法用於建立檔案,本地方法邏輯是,
- 如果是根目錄則不做任何操作,因為根目錄肯定存在的。
- 通過 handleOpen 函式完成建立的邏輯,該方法其實通過呼叫 open64 函式建立檔案,其中傳入的引數為 O_RDWR | O_CREAT | O_EXCL,表示以讀寫模式開啟,並且如果檔案不存在則建立,如果檔案已存在則返回-1。
- 建立成功後通過 fstat64 函式獲取檔案屬性,用 S_ISDIR 函式判斷如果是目錄則關閉檔案並且設定 errno 為 EISDIR。
- 如果 handleOpen 函式返回了-1,則如果 errno 不為 EEXIST 則丟擲錯誤,也就是說檔案已經存在的話則不丟擲錯誤。
- 最後建立檔案成功後關閉它,返回成功。
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核心設計剖析》已經在京東銷售了,有需要的朋友可以購買。感謝各位朋友。
=========================
相關閱讀:
JDK不同作業系統的FileSystem(Windows)上篇
JDK不同作業系統的FileSystem(Windows)中篇
JDK不同作業系統的FileSystem(Windows)下篇
JDK不同作業系統的FileSystem(unix-like)上篇
歡迎關注: