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,一樣地,其餘操做系統也有本身的文件系統實現類。c++

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

繼承結構

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

類定義

class WinNTFileSystem extends FileSystem複製代碼

主要屬性

  • slash 表示斜槓符號。
  • altSlash 與slash相反的斜槓。
  • semicolon 表示分號。
  • driveDirCache 表示驅動盤目錄緩存。
  • cache 用於緩存標準路徑。
  • prefixCache 用於緩存標準路徑前綴。
    private final char slash;
      private final char altSlash;
      private final char semicolon;
      private static String[] driveDirCache = new String[26];
      private ExpiringCache cache       = new ExpiringCache();
      private ExpiringCache prefixCache = new ExpiringCache();複製代碼

主要方法

構造方法

構造方法很簡單,先經過 System.getProperties() 獲取 Properties 對象,而後獲取其裏面的 file.separator 屬性和 path.separator 屬性的值, 分別賦值給相應變量,在 Windows 中這兩個值分別爲 \ 和 ; 。最後將斜槓 / 賦給 altSlash。安全

public WinNTFileSystem() {
        Properties props = GetPropertyAction.privilegedGetProperties();
        slash = props.getProperty("file.separator").charAt(0);
        semicolon = props.getProperty("path.separator").charAt(0);
        altSlash = (this.slash == '\\') ? '/' : '\\';
    }複製代碼

isSlash方法

判斷是否是斜槓。bash

private boolean isSlash(char c) {
        return (c == '\\') || (c == '/');
    }複製代碼

isLetter方法

判斷是否是字母。app

private boolean isLetter(char c) {
        return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
    }複製代碼

slashify方法

判斷一個字符串是否以斜槓開頭,不是則幫其開頭添加斜槓,是則不做處理。函數

private String slashify(String p) {
        if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p;
        else return p;
    }複製代碼

normalize方法

該方法主要是對路徑進行標準化,它在實現過程當中依賴另一個 normalize 方法和 normalizePrefix 方法,這兩個方法都是 private 的。ui

針對傳入來的 path 變量,用一個 for 循環遍歷每一個字符,分別對如下三種狀況處理,this

  1. 當遇到 altSlash 時,即 / 時,則把 path 傳入另一個 normalize 方法中進行處理,其中涉及 prev == slash 判斷條件,prev 其實就是前一個字符,相等就說明兩個 / 連着。
  2. 當遇到連續兩個 slash 時,即連續兩個 \ 時,並且 i 還要大於1時,則把 path 傳入另一個 normalize 方法中進行處理。
  3. 當遇到 : 字符且 i 大於1時,則把 path 傳入另一個 normalize 方法中進行處理。

若是都不在上述狀況內,則要繼續判斷最後一個字符是否爲 slash ,若是是則還要傳入另一個 normalize 方法中進行處理。不然直接返回 path ,這時其實 path 就是以一個或兩個 \ 開頭且後面再也不存在斜槓或反斜槓或冒號,這種狀況是能夠直接返回的。

public String normalize(String path) {
        int n = path.length();
        char slash = this.slash;
        char altSlash = this.altSlash;
        char prev = 0;
        for (int i = 0; i < n; i++) {
            char c = path.charAt(i);
            if (c == altSlash)
                return normalize(path, n, (prev == slash) ? i - 1 : i);
            if ((c == slash) && (prev == slash) && (i > 1))
                return normalize(path, n, i - 1);
            if ((c == ':') && (i > 1))
                return normalize(path, n, 0);
            prev = c;
        }
        if (prev == slash) return normalize(path, n, n - 1);
        return path;
    }複製代碼

往下看具體的處理邏輯,這裏有三個參數,第一個是路徑字符串,第二個是路徑長度,第三個是路徑字符串的偏移,偏移量用來表示從哪一個位置開始,偏移量 off 不能小於3,這是考慮到了UNC路徑。繼續往下若是偏移量等於0的話則先處理前綴,這時調用 normalizePrefix 方法處理。偏移量非0的狀況下則表示已經有部分已經標準化好了,將其先 append 到 StringBuilder 對象中。

接着開始處理從偏移量開始到結尾的路徑,用 while 循環遍歷剩餘路徑中的每一個字符,若是有連着都是斜槓的狀況則跳太重複的斜槓,這裏斜槓包括了 /\ 。非斜槓的狀況則直接將字符 append 到 StringBuilder 對象中,多個斜槓則只添加一個斜槓。最後 src == len 條件則表示已經到結尾了,這時要考慮一些特殊狀況的處理,好比 c:\\\\\\\\

private String normalize(String path, int len, int off) {
      if (len == 0) return path;
      if (off < 3) off = 0;   
      int src;
      char slash = this.slash;
      StringBuilder sb = new StringBuilder(len);
      if (off == 0) {
          src = normalizePrefix(path, len, sb);
      } else {
          src = off;
          sb.append(path, 0, off);
      }
      while (src < len) {
          char c = path.charAt(src++);
          if (isSlash(c)) {
              while ((src < len) && isSlash(path.charAt(src))) src++;
              if (src == len) {
                  int sn = sb.length();
                  if ((sn == 2) && (sb.charAt(1) == ':')) {
                      sb.append(slash);
                      break;
                  }
                  if (sn == 0) {
                      sb.append(slash);
                      break;
                  }
                  if ((sn == 1) && (isSlash(sb.charAt(0)))) {
                      sb.append(slash);
                      break;
                  }
                  break;
              } else {
                  sb.append(slash);
              }
          } else {
              sb.append(c);
          }
      }
      return sb.toString();
  }複製代碼

正常狀況下,Windows的路徑不會存在連着的兩個斜槓(除了UNC路徑可能會兩個斜槓開頭),同時也不會以斜槓結束。路徑通常分爲:目錄相對路徑、驅動盤相對路徑、UNC絕對路徑和本地絕對路徑。如下兩種邏輯分別處理相似c:\\

private int normalizePrefix(String path, int len, StringBuilder sb) {
      int src = 0;
      while ((src < len) && isSlash(path.charAt(src))) src++;
      char c;
      if ((len - src >= 2)
          && isLetter(c = path.charAt(src))
          && path.charAt(src + 1) == ':') {
          sb.append(c);
          sb.append(':');
          src += 2;
      } else {
          src = 0;
          if ((len >= 2)
              && isSlash(path.charAt(0))
              && isSlash(path.charAt(1))) {
              src = 1;
              sb.append(slash);
          }
      }
      return src;
  }複製代碼

綜上處理邏輯,爲幫助咱們更好地理解,用如下不一樣路徑格式看看對應的標準化後是什麼樣的。

System.out.println(f.normalize("d:\\\\test\\test////"));
      System.out.println(f.normalize("d://test\\test////"));
      System.out.println(f.normalize("d://test\\test////test.txt"));
      System.out.println(f.normalize("d:\\test/test\\\\"));
      System.out.println(f.normalize("d:\\test/test/"));
      System.out.println(f.normalize("d:/test/test//"));
      System.out.println(f.normalize("test\\"));
      System.out.println(f.normalize("\\"));
      System.out.println(f.normalize("/"));
      System.out.println(f.normalize("c:\\"));
      System.out.println(f.normalize("c:test"));
      System.out.println(f.normalize("/c:/test"));
      System.out.println(f.normalize("file://c:/test"));
      System.out.println(f.normalize("\\\\test\\"));
      System.out.println(f.normalize("\\\\test/"));複製代碼
d:\test\test
d:\test\test
d:\test\test\test.txt
d:\test\test
d:\test\test
d:\test\test
test
\
\
c:\
c:test
c:\test
file:\c:\test
\\test
\\test複製代碼

prefixLength方法

該方法主要是獲取路徑前綴的長度。按照順序看下邏輯,獲取第一個第二個字符,若是都爲 slash ,即兩個\,則爲 UNC 路徑,形如 \\test,返回2;若是第二個字符不是\則爲驅動盤相對路徑,形如\test,返回1;當第一個字符爲字母且第二個爲:時,若是第三個字符爲\,則爲本地絕對路徑,形如c:\test,返回3;若是第三個字符爲非\,則爲目錄相對路徑,形如c:test;最後則爲相對路徑,形如test

public int prefixLength(String path) {
        char slash = this.slash;
        int n = path.length();
        if (n == 0) return 0;
        char c0 = path.charAt(0);
        char c1 = (n > 1) ? path.charAt(1) : 0;
        if (c0 == slash) {
            if (c1 == slash) return 2;  
            return 1;                   
        }
        if (isLetter(c0) && (c1 == ':')) {
            if ((n > 2) && (path.charAt(2) == slash))
                return 3;               
            return 2;                   
        }
        return 0;                       
    }複製代碼

getUserPath方法

經過 System 獲取 user.dir 屬性做爲用戶路徑。

private String getUserPath() {
        return normalize(System.getProperty("user.dir"));
    }複製代碼

getDrive方法

獲取驅動盤,先獲取路徑頭部長度,再截取驅動盤。

private String getDrive(String path) {
        int pl = prefixLength(path);
        return (pl == 3) ? path.substring(0, 2) : null;
    }複製代碼

driveIndex方法

獲取驅動盤的索引值,按照字母順序,好比 a 或 A 則索引值爲0。

private static int driveIndex(char d) {
        if ((d >= 'a') && (d <= 'z')) return d - 'a';
        if ((d >= 'A') && (d <= 'Z')) return d - 'A';
        return -1;
    }複製代碼

getDriveDirectory方法

獲取指定驅動盤下的工做目錄,每一個驅動盤都有工做目錄。能夠看到有兩個 getDriveDirectory 方法,其中一個本地方法,實現須要本地方法來支持。其中邏輯是先根據驅動盤獲取對應的驅動盤索引,而後再將索引加一併經過本地方法獲取對應驅動盤當前工做目錄,這裏還會將其緩存起來,方便後面查詢。

private native String getDriveDirectory(int drive);

    private String getDriveDirectory(char drive) {
        int i = driveIndex(drive);
        if (i < 0) return null;
        String s = driveDirCache[i];
        if (s != null) return s;
        s = getDriveDirectory(i + 1);
        driveDirCache[i] = s;
        return s;
    }複製代碼

本地的實現以下,主要看函數 currentDir,先經過操做系統的API函數 GetDriveTypeW 判斷是否爲不合格的驅動盤類型,這其中參數都是用寬字符。接着經過 _wgetdcwd 函數獲取指定驅動器上的當前工做目錄的完整路徑,同時去掉驅動盤和冒號,返回給 Java 層一個表示當前工做目錄路徑的字符串。

JNIEXPORT jobject JNICALL
Java_java_io_WinNTFileSystem_getDriveDirectory(JNIEnv *env, jobject this,
                                               jint drive)
{
    jstring ret = NULL;
    jchar *p = currentDir(drive);
    jchar *pf = p;
    if (p == NULL) return NULL;
    if (iswalpha(*p) && (p[1] == L':')) p += 2;
    ret = (*env)->NewString(env, p, (jsize)wcslen(p));
    free (pf);
    return ret;
}複製代碼
WCHAR*
currentDir(int di) {
    UINT dt;
    WCHAR root[4];
    root[0] = L'A' + (WCHAR)(di - 1);
    root[1] = L':';
    root[2] = L'\\';
    root[3] = L'\0';
    dt = GetDriveTypeW(root);
    if (dt == DRIVE_UNKNOWN || dt == DRIVE_NO_ROOT_DIR) {
        return NULL;
    } else {
        return _wgetdcwd(di, NULL, MAX_PATH);
    }
}複製代碼

resolve方法

有兩個resolve方法。

第一個 resolve 方法主要是針對傳入的兩個參數,一個是父路徑一個是子路徑,對它們進行解析而後獲得一個新路徑。此過程須要考慮兩個路徑的格式。邏輯以下:

  1. 先分別獲取父路徑長度和子路徑長度。
  2. 根據父路徑判斷是否爲目錄相對路徑,形如c:的。
  3. 若子路徑以 slash 即\開頭,則多是 UNC 路徑,這時要丟棄它的頭部,因此子路徑從第2的位置開始;也多是驅動盤相對路徑,這時丟棄它的頭部,子路徑從第1的位置開始;最後若是子路徑爲兩個 slash 即 \\時,則直接返回父路徑,固然父路徑若是以 slash 結尾也要將其去掉。
  4. 此時肯定好了父路徑的長度、父路徑的結束位置、子路徑的長度和子路徑的開始位置,就能夠獲得最終的新路徑的長度了。
  5. 根據上述的長度和位置信息將父路徑和子路徑合併,返回一個新的路徑。
public String resolve(String parent, String child) {
        int pn = parent.length();
        if (pn == 0) return child;
        int cn = child.length();
        if (cn == 0) return parent;

        String c = child;
        int childStart = 0;
        int parentEnd = pn;

        boolean isDirectoryRelative =
            pn == 2 && isLetter(parent.charAt(0)) && parent.charAt(1) == ':';

        if ((cn > 1) && (c.charAt(0) == slash)) {
            if (c.charAt(1) == slash) {
                childStart = 2;
            } else if (!isDirectoryRelative) {
                childStart = 1;

            }
            if (cn == childStart) { 
                if (parent.charAt(pn - 1) == slash)
                    return parent.substring(0, pn - 1);
                return parent;
            }
        }

        if (parent.charAt(pn - 1) == slash)
            parentEnd--;

        int strlen = parentEnd + cn - childStart;
        char[] theChars = null;
        if (child.charAt(childStart) == slash || isDirectoryRelative) {
            theChars = new char[strlen];
            parent.getChars(0, parentEnd, theChars, 0);
            child.getChars(childStart, cn, theChars, parentEnd);
        } else {
            theChars = new char[strlen + 1];
            parent.getChars(0, parentEnd, theChars, 0);
            theChars[parentEnd] = slash;
            child.getChars(childStart, cn, theChars, parentEnd + 1);
        }
        return new String(theChars);
    }複製代碼

第二個 resolve 方法傳入的是 File,主要是根據 File 對應的不一樣類型路徑解析處理而後返回。

  1. 獲取路徑頭部。
  2. 若是頭部長爲2且以\開頭,此時爲 UNC 路徑,直接返回路徑。
  3. 若是頭部長爲3,則爲本地絕對路徑,直接返回路徑。
  4. 若是長度爲0,則爲相對路徑,返回用戶路徑+此相對路徑。
  5. 若是長度爲1,則爲驅動盤相對路徑,此時嘗試根據用戶路徑獲取驅動盤,存在驅動盤則返回驅動盤+此路徑,不存在驅動盤則說明用戶路徑是一個 UNC 路徑,返回用戶路徑+此路徑。
  6. 若是頭部長度爲2,則爲目錄相對路徑。此時先獲取用戶路徑,再根據用戶路徑獲取對應驅動盤,若是路徑以驅動盤開頭,則直接返回用戶路徑+去掉驅動盤後的路徑。若是繼續往下則經過 getDriveDirectory 獲取指定驅動盤的工做目錄,將驅動盤+:+工做目錄+路徑等拼接起來獲得最終的新路徑,而後還要用安全管理器檢查是否有讀的權限。
public String resolve(File f) {
        String path = f.getPath();
        int pl = f.getPrefixLength();
        if ((pl == 2) && (path.charAt(0) == slash))
            return path;                       
        if (pl == 3)
            return path;                        
        if (pl == 0)
            return getUserPath() + slashify(path); 
        if (pl == 1) {                          
            String up = getUserPath();
            String ud = getDrive(up);
            if (ud != null) return ud + path;
            return up + path;                   
        }
        if (pl == 2) {                
            String up = getUserPath();
            String ud = getDrive(up);
            if ((ud != null) && path.startsWith(ud))
                return up + slashify(path.substring(2));
            char drive = path.charAt(0);
            String dir = getDriveDirectory(drive);
            String np;
            if (dir != null) {
                String p = drive + (':' + dir + slashify(path.substring(2)));
                SecurityManager security = System.getSecurityManager();
                try {
                    if (security != null) security.checkRead(p);
                } catch (SecurityException x) {
                    throw new SecurityException("Cannot resolve path " + path);
                }
                return p;
            }
            return drive + ":" + slashify(path.substring(2)); 
        }
        throw new InternalError("Unresolvable path: " + path);
    }複製代碼

如下是廣告

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

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

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

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

歡迎關注:

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

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