咱們知道不一樣的操做系統有各自的文件系統,這些文件系統又存在不少差別,而Java 由於是跨平臺的,因此它必需要統一處理這些不一樣平臺文件系統之間的差別,才能往上提供統一的入口。java
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 類。篇幅所限,分爲上中下篇,此爲上篇。bash
--java.lang.Object
--java.io.FileSystem
--java.io.UnixFileSystem
複製代碼
class UnixFileSystem extends FileSystem
複製代碼
private final char slash;
private final char colon;
private final String javaHome;
private ExpiringCache cache = new ExpiringCache();
private ExpiringCache javaHomePrefixCache = new ExpiringCache();
複製代碼
構造方法很簡答,直接從 System 中獲取到 Properties ,而後再分別根據 file.separator 、 path.separator 和 java.home 獲取對應的屬性值並賦給 UnixFileSystem 對象的屬性。併發
public UnixFileSystem() {
Properties props = GetPropertyAction.privilegedGetProperties();
slash = props.getProperty("file.separator").charAt(0);
colon = props.getProperty("path.separator").charAt(0);
javaHome = props.getProperty("java.home");
}
複製代碼
其中的 GetPropertyAction.privilegedGetProperties()
其實就是 System.getProperties()
,這裏只是將安全管理器相關的處理抽離出來而已。app
public static Properties privilegedGetProperties() {
if (System.getSecurityManager() == null) {
return System.getProperties();
} else {
return AccessController.doPrivileged(
new PrivilegedAction<Properties>() {
public Properties run() {
return System.getProperties();
}
}
);
}
}
複製代碼
該方法主要是對路徑進行標準化, unix-like 的路徑標準化可比 Windows 簡單,不像 Windows 狀況複雜且還要調用本地方法處理。機器學習
有兩個 normalize 方法,第一個 normalize 方法主要是負責檢查路徑是否標準,若是不是標準的則要傳入第二個 normalize 方法進行標準化處理。而判斷路徑是否標準的邏輯主要有兩個,分佈式
/
。/
結尾。public String normalize(String pathname) {
int n = pathname.length();
char prevChar = 0;
for (int i = 0; i < n; i++) {
char c = pathname.charAt(i);
if ((prevChar == '/') && (c == '/'))
return normalize(pathname, n, i - 1);
prevChar = c;
}
if (prevChar == '/') return normalize(pathname, n, n - 1);
return pathname;
}
複製代碼
進入到路徑標準處理後的邏輯以下,函數
/
,主要做用是去掉尾部多餘的斜槓,若是所有都是/
(好比///////
)則直接返回/
。/
則直接跳過,這個其實就是隻保留一個/
。private String normalize(String pathname, int len, int off) {
if (len == 0) return pathname;
int n = len;
while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--;
if (n == 0) return "/";
StringBuilder sb = new StringBuilder(pathname.length());
if (off > 0) sb.append(pathname, 0, off);
char prevChar = 0;
for (int i = off; i < n; i++) {
char c = pathname.charAt(i);
if ((prevChar == '/') && (c == '/')) continue;
sb.append(c);
prevChar = c;
}
return sb.toString();
}
複製代碼
該方法用於返回路徑前綴長度,對於傳進來的標準路徑,以/
開始則返回1,不然返回0。學習
public int prefixLength(String pathname) {
if (pathname.length() == 0) return 0;
return (pathname.charAt(0) == '/') ? 1 : 0;
}
複製代碼
有兩個 resolve 方法,第一個方法用於合併父路徑和子路徑獲得一個新的路徑,邏輯爲,
/
開頭的狀況下,若是父路徑爲/
則直接返回子路徑,不然則返回父路徑+子路徑。/
則返回父路徑+子路徑。/
+子路徑。public String resolve(String parent, String child) {
if (child.equals("")) return parent;
if (child.charAt(0) == '/') {
if (parent.equals("/")) return child;
return parent + child;
}
if (parent.equals("/")) return parent + child;
return parent + '/' + child;
}
public String resolve(File f) {
if (isAbsolute(f)) return f.getPath();
return resolve(System.getProperty("user.dir"), f.getPath());
}
複製代碼
第二個 resolve 方法用於兼容處理 File 對象,邏輯是,
user.dir
屬性值做爲父路徑,而後 File 對象對應的路徑做爲子路徑,再調用第一個 resolve 方法合併父路徑和子路徑。該方法獲取默認父路徑,直接返回/
。
public String getDefaultParent() {
return "/";
}
複製代碼
該方法主要是格式化路徑。主要邏輯是完成相似如下的轉換處理:
/root/
--> /root
。/
--> /
,這是經過長度來限制的,即當長度超過1時纔會去掉尾部的 /
。public String fromURIPath(String path) {
String p = path;
if (p.endsWith("/") && (p.length() > 1)) {
p = p.substring(0, p.length() - 1);
}
return p;
}
複製代碼
該方法判斷 File 對象是否爲絕對路徑,直接根據 File 類的 getPrefixLength 方法獲取前綴長度是否爲0做爲判斷條件,該方法最終就是調用該類的 prefixLength 方法,有前綴就說明是絕對路徑。
public boolean isAbsolute(File f) {
return (f.getPrefixLength() != 0);
}
複製代碼
該方法用來標準化某路徑,標準路徑不只是一個絕對路徑並且仍是惟一的路徑,並且標準的定義是依賴於操做系統的。比較典型的就是處理包含"."或".."的路徑,還有符號連接等。下面看 unix-like 操做系統如何標準化路徑:
public String canonicalize(String path) throws IOException {
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 = javaHomePrefixCache.get(dir);
if (resDir != null) {
String filename = path.substring(1 + dir.length());
res = resDir + slash + filename;
cache.put(dir + slash + filename, res);
}
}
}
if (res == null) {
res = canonicalize0(path);
cache.put(path, res);
if (useCanonPrefixCache &&
dir != null && dir.startsWith(javaHome)) {
resDir = parentOrNull(res);
if (resDir != null && resDir.equals(dir)) {
File f = new File(res);
if (f.exists() && !f.isDirectory()) {
javaHomePrefixCache.put(dir, resDir);
}
}
}
}
}
return res;
}
}
private native String canonicalize0(String path) throws IOException;
複製代碼
本地方法 canonicalize0 以下,處理邏輯經過 canonicalize 函數實現,因爲函數較長,這裏再也不貼出來,主要的處理邏輯:
/./
/../
符號的表示、多餘的/
符號。但有時對於一些特殊的非正常寫法可能致使沒法經過 realpath 函數處理掉,好比...
或....
,因此接着還得再判斷是否須要進一步處理,須要則繼續處理,不然直接返回路徑。...
或....
狀況處理掉並返回標準路徑。JNIEXPORT jstring JNICALL
Java_java_io_UnixFileSystem_canonicalize0(JNIEnv *env, jobject this,
jstring pathname)
{
jstring rv = NULL;
WITH_PLATFORM_STRING(env, pathname, path) {
char canonicalPath[JVM_MAXPATHLEN];
if (canonicalize((char *)path,
canonicalPath, JVM_MAXPATHLEN) < 0) {
JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
} else {
#ifdef MACOSX
rv = newStringPlatform(env, canonicalPath);
#else
rv = JNU_NewStringPlatform(env, canonicalPath);
#endif
}
} END_PLATFORM_STRING(env, path);
return rv;
}
複製代碼
如下是***廣告***和***相關閱讀***
=============廣告時間===============
公衆號的菜單已分爲「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。
鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠購買。感謝各位朋友。
=========================
相關閱讀:
JDK不一樣操做系統的FileSystem(Windows)上篇
JDK不一樣操做系統的FileSystem(Windows)中篇
JDK不一樣操做系統的FileSystem(Windows)下篇
歡迎關注: