专栏名称: 蚂蚁金服ProtoTeam
数据前端团队
目录
相关文章推荐
IT服务圈儿  ·  45K*16薪,进字节了! ·  昨天  
前端早读课  ·  【第3459期】两款 AI 编程助手 ... ·  昨天  
前端之巅  ·  Dagger:我们用 GO 和 ... ·  2 天前  
前端早读课  ·  【第3458期】React ... ·  2 天前  
51好读  ›  专栏  ›  蚂蚁金服ProtoTeam

JDK不同操作系统的FileSystem(unix-like)上篇

蚂蚁金服ProtoTeam  · 掘金  · 前端  · 2017-12-08 01:25

正文

前言

我们知道不同的操作系统有各自的文件系统,这些文件系统又存在很多差异,而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();

主要方法

构造方法

构造方法很简答,直接从 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() ,这里只是将安全管理器相关的处理抽离出来而已。

    public static Properties privilegedGetProperties() {
        if (System.getSecurityManager() == null) {
            return System.getProperties();
        } else {
            return AccessController.doPrivileged(
                    new PrivilegedAction<Properties>() {
                        public Properties run() {
                            return System.getProperties();
                        }
                    }
            );
        }
    }

normalize方法

该方法主要是对路径进行标准化, unix-like 的路径标准化可比 Windows 简单,不像 Windows 情况复杂且还要调用本地方法处理。

有两个 normalize 方法,第一个 normalize 方法主要是负责检查路径是否标准,如果不是标准的则要传入第二个 normalize 方法进行标准化处理。而判断路径是否标准的逻辑主要有两个,

  1. 路径中是否有连着2个以上 /
  2. 路径是否以 / 结尾。
    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;
    }

进入到路径标准处理后的逻辑如下,

  1. 长度为0则直接返回传入的路径。
  2. 用 while 循环从尾部向前搜索 / ,主要作用是去掉尾部多余的斜杠,如果全部都是 / (比如 /////// )则直接返回 /
  3. off 变量表示偏移量,这个是由第一个 normalize 方法遍历得出的,此变量前面的路径表示符合标准化要求,无需再做标准化处理。直接截取其前面的字符串。
  4. 用 for 循环处理剩下的路径,遇到连着两个 / 则直接跳过,这个其实就是只保留一个 /
    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();
    }

prefixLength方法

该方法用于返回路径前缀长度,对于传进来的标准路径,以 / 开始则返回1,否则返回0。

    public int prefixLength(String pathname) {
        if (pathname.length() == 0) return 0;
        return (pathname.charAt(0) == '/') ? 1 : 0;
    }

resolve方法

有两个 resolve 方法,第一个方法用于合并父路径和子路径得到一个新的路径,逻辑为,

  1. 如果子路径为空则直接返回父路径。
  2. 在子路径以 / 开头的情况下,如果父路径为 / 则直接返回子路径,否则则返回父路径+子路径。
  3. 如果父路径为 / 则返回父路径+子路径。
  4. 以上都不是则返回父路径+ / +子路径。
    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 对象,逻辑是,

  1. 如果是绝对路径则直接返回 File 对象的路径。
  2. 否则则从 System 中获取 user.dir 属性值作为父路径,然后 File 对象对应的路径作为子路径,再调用第一个 resolve 方法合并父路径和子路径。

getDefaultParent方法

该方法获取默认父路径,直接返回 /

    public String getDefaultParent() {
        return "/";
    }

fromURIPath方法

该方法主要是格式化路径。主要逻辑是完成类似以下的转换处理:

  1. /root/ --> /root
  2. 但是 / --> / ,这是通过长度来限制的,即当长度超过1时才会去掉尾部的 /
    public String fromURIPath(String path) {
        String p = path;
        if (p.endsWith("/") && (p.length() > 1)) {
            p = p.substring(0, p.length() - 1);
        }
        return p;
    }

isAbsolute方法

该方法判断 File 对象是否为绝对路径,直接根据 File 类的 getPrefixLength 方法获取前缀长度是否为0作为判断条件,该方法最终就是调用该类的 prefixLength 方法,有前缀就说明是绝对路径。

    public boolean isAbsolute(File f) {
        return (f.getPrefixLength() != 0);
    }

canonicalize方法

该方法用来标准化某路径,标准路径不仅是一个绝对路径而且还是唯一的路径,而且标准的定义是依赖于操作系统的。比较典型的就是处理包含"."或".."的路径,还有符号链接等。下面看 unix-like 操作系统如何标准化路径:

  1. 如果不使用缓存则直接调用 canonicalize0 本地方法获取标准化路径。
  2. 如果使用了缓存则在缓存中查找,存在则直接返回,否则先调用 canonicalize0 本地方法获取标准化路径,再将路径放进缓存中。
  3. 另外,还提供了前缀缓存可以使用,它缓存了标准路径的父目录,这样就可以节省了前缀部分的处理,前缀缓存的逻辑也是第一次标准化后将其缓存起来,下次则可从前缀缓存中查询。
  4. 使用前缀缓存这里有一个条件,就是必须是在Java Home目录下的文件才能被缓存,否则不予许。前缀缓存的使用节省了一些工作,提高效率。
    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






请到「今天看啥」查看全文