我们知道不同的操作系统有各自的文件系统,这些文件系统又存在很多差异,而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 类。篇幅所限,分为上中下篇,此为中篇。
- --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();
该方法用于获取路径的父目录,它的思路其实很简单,就是从路径的最后一个字符开始向前寻找路径分隔符(斜杠),正常情况下,找到第一个路径分隔符的位置,再从头开始截取到该位置即可。但可能存在
或
- .
的情况,所以如果连续遇到两个
- ..
则返回 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;
- }
这是一个本地方法,它主要的作用是可以用来判断 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;
- }
这是一个本地方法,它主要的作用是判断某个文件或目录是否可读、是否可写、是否可执行。这里同样用位标识这些属性,分别用
表示可执行、可写、可读。
- 0x01、0x02、0x04
- public native boolean checkAccess(File f, int access);
本地方法的实现主要通过 access 函数,可以看到可读、可写和可执行三种情况对应的mode分别为
、
- R_OK
、
- W_OK
。拥有对应权限则返回0,否则返回-1。最后往Java层返回一个 boolean 值。
- X_OK
- 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;
- }
该方法用于获取文件或目录的最后修改时间,本地方法逻辑是通过 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;");
- }
该方法用于获取文件或目录的长度,本地方法逻辑是通过 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;
- }
该方法主要用于设置 File 对象的访问权限,核心逻辑是通过 chmod 函数来实现,具体逻辑如下:
,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IRUSR 或 S_IRUSR | S_IRGRP | S_IROTH。
- ACCESS_READ
,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IWUSR 或 S_IWUSR | S_IWGRP | S_IWOTH。
- ACCESS_WRITE
,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IXUSR 或 S_IXUSR | S_IXGRP | S_IXOTH。
- ACCESS_EXECUTE
- 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;
- }
该方法用于创建文件,本地方法逻辑是,
- 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;
- }
该方法用于删除 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内核设计剖析》
=========================
来源: https://juejin.im/post/5a2be7a1f265da43176a11a1