JDK不一样操做系统的FileSystem(unix-like)中篇

前言

咱们知道不一样的操做系统有各自的文件系统,这些文件系统又存在不少差别,而Java 由于是跨平台的,因此它必需要统一处理这些不一样平台文件系统之间的差别,才能往上提供统一的入口。java

关于FileSystem类

JDK 里面抽象出了一个 FileSystem 来表示文件系统,不一样的操做系统经过继承该类实现各自的文件系统,好比 Windows NT/2000 操做系统则为 WinNTFileSystem,而 unix-like 操做系统为 UnixFileSystem。缓存

须要注意的一点是,WinNTFileSystem类 和 UnixFileSystem类并非在同一个 JDK 里面,也就是说它们是分开的,你只能在 Windows 版本的 JDK 中找到 WinNTFileSystem,而在 unix-like 版本的 JDK 中找到 UnixFileSystem,一样地,其余操做系统也有本身的文件系统实现类。bash

这里分红两个系列分析 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 类型呢?由于这里为了高效利用数据,用位做为不一样属性的标识,分别为 0x0一、0x0二、0x0四、0x08,分别表明是否存在、是否为文件、是否为目录和是否为隐藏文件或目录。学习

public native int getBooleanAttributes0(File f);
复制代码

本地方法的主要逻辑是经过 stat64 函数获取文件属性,在经过或运算将属性信息装进整型数值中。ui

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方法

这是一个本地方法,它主要的做用是判断某个文件或目录是否可读、是否可写、是否可执行。这里一样用位标识这些属性,分别用0x0一、0x0二、0x04表示可执行、可写、可读。this

public native boolean checkAccess(File f, int access);
复制代码

本地方法的实现主要经过 access 函数,能够看到可读、可写和可执行三种状况对应的mode分别为R_OKW_OKX_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 函数来实现,具体逻辑以下:

  1. 对于 owneronly 变量,表示是否仅修改文件全部者的权限。
  2. 若是设为 ACCESS_READ,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IRUSR 或 S_IRUSR | S_IRGRP | S_IROTH。
  3. 若是设为 ACCESS_WRITE,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IWUSR 或 S_IWUSR | S_IWGRP | S_IWOTH。
  4. 若是设为 ACCESS_EXECUTE,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IXUSR 或 S_IXUSR | S_IXGRP | S_IXOTH。
  5. 经过 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方法

该方法用于建立文件,本地方法逻辑是,

  1. 若是是根目录则不作任何操做,由于根目录确定存在的。
  2. 经过 handleOpen 函数完成建立的逻辑,该方法其实经过调用 open64 函数建立文件,其中传入的参数为 O_RDWR | O_CREAT | O_EXCL,表示以读写模式打开,而且若是文件不存在则建立,若是文件已存在则返回-1。
  3. 建立成功后经过 fstat64 函数获取文件属性,用 S_ISDIR 函数判断若是是目录则关闭文件而且设置 errno 为 EISDIR。
  4. 若是 handleOpen 函数返回了-1,则若是 errno 不为 EEXIST 则抛出错误,也就是说文件已经存在的话则不抛出错误。
  5. 最后建立文件成功后关闭它,返回成功。
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内核设计剖析》已经在京东销售了,有须要的朋友能够购买。感谢各位朋友。

为何写《Tomcat内核设计剖析》

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

相关阅读:

JDK不一样操做系统的FileSystem(Windows)上篇

JDK不一样操做系统的FileSystem(Windows)中篇

JDK不一样操做系统的FileSystem(Windows)下篇

JDK不一样操做系统的FileSystem(unix-like)上篇

欢迎关注:

这里写图片描述
相关文章
相关标签/搜索