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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

作者:超人汪小建(seaboat)

出处:https://blog.csdn.net/wangyangzhizhou/column/info/16032


前言

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

主要方法

list方法

该方法用于列出指定目录下的所有文件和目录,本地方法处理逻辑如下,
1. 获取 java/lang/String类对象,并检查不能为NULL。
2. 通过 opendir 函数打开指定路径目录,为空的话则返回空。
3. 开辟 sizeof(struct dirent64) + (PATH_MAX + 1) 大小的空间,PATH_MAX为1024。
4. 通过 NewObjectArray 开辟一个初始大小为 maxlen 即16的字符串数组,然后使用循环不断调用 readdir64_r 函数获取目录下的元素,并存放到字符串数组中。这个过程中如果数组满了,则按照原先数组大小成倍扩扩展,扩展后将原数组的值复制到新数组中。
5. 完成后关闭目录并释放 dirent64。
6. 执行完上述操作,代表目录下的文件和目录的字符串数组大小就已经确定了,接着按照该大小再一次用 NewObjectArray 开辟一个字符串数组,并把原来数组的值复制过来,这就是最终的字符串数据了。
7. 中途遇到错误则会跳转到 error 标签处,执行关闭目录和释放资源,并返回空。

    private native boolean delete0(File f);

    JNIEXPORT jobjectArray JNICALL
    Java_java_io_UnixFileSystem_list(JNIEnv *env, jobject this,
                                     jobject file)
    {
        DIR *dir = NULL;
        struct dirent64 *ptr;
        struct dirent64 *result;
        int len, maxlen;
        jobjectArray rv, old;
        jclass str_class;

        str_class = JNU_ClassString(env);
        CHECK_NULL_RETURN(str_class, NULL);

        WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
            dir = opendir(path);
        } END_PLATFORM_STRING(env, path);
        if (dir == NULL) return NULL;

        ptr = malloc(sizeof(struct dirent64) + (PATH_MAX + 1));
        if (ptr == NULL) {
            JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
            closedir(dir);
            return NULL;
        }

        len = 0;
        maxlen = 16;
        rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
        if (rv == NULL) goto error;

        while ((readdir64_r(dir, ptr, &result) == 0)  && (result != NULL)) {
            jstring name;
            if (!strcmp(ptr->d_name, ".") || !strcmp(ptr->d_name, ".."))
                continue;
            if (len == maxlen) {
                old = rv;
                rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
                if (rv == NULL) goto error;
                if (JNU_CopyObjectArray(env, rv, old, len) < 0) goto error;
                (*env)->DeleteLocalRef(env, old);
            }
    #ifdef MACOSX
            name = newStringPlatform(env, ptr->d_name);
    #else
            name = JNU_NewStringPlatform(env, ptr->d_name);
    #endif
            if (name == NULL) goto error;
            (*env)->SetObjectArrayElement(env, rv, len++, name);
            (*env)->DeleteLocalRef(env, name);
        }
        closedir(dir);
        free(ptr);

        old = rv;
        rv = (*env)->NewObjectArray(env, len, str_class, NULL);
        if (rv == NULL) {
            return NULL;
        }
        if (JNU_CopyObjectArray(env, rv, old, len) < 0) {
            return NULL;
        }
        return rv;

     error:
        closedir(dir);
        free(ptr);
        return NULL;
    }

createDirectory方法

该方法用来创建目录,本地方法很简单,就是获取 File 对象对应的路径,再通过 mkdir 函数创建目录。其中0777,表示文件所有者、文件所有者所在的组的用户、其他用户,都有权限进行读、写、执行的操作。

    public native boolean createDirectory(File f);

    JNIEXPORT jboolean JNICALL
    Java_java_io_UnixFileSystem_createDirectory(JNIEnv *env, jobject this,
                                                jobject file)
    {
        jboolean rv = JNI_FALSE;

        WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
            if (mkdir(path, 0777) == 0) {
                rv = JNI_TRUE;
            }
        } END_PLATFORM_STRING(env, path);
        return rv;
    }

rename方法

该方法用于重命名文件,需要将标准路径缓存和标准路径前缀缓存都清掉,然后调用本地方法 rename0 执行重命名操作。

    public boolean rename(File f1, File f2) {
            cache.clear();
            javaHomePrefixCache.clear();
            return rename0(f1, f2);
        }

    private native boolean rename0(File f1, File f2);

本地方法主要调用了 rename 函数,根据 Java 层传入的两个 File 对象对应的路径进行重命名。

    JNIEXPORT jboolean JNICALL
    Java_java_io_UnixFileSystem_rename0(JNIEnv *env, jobject this,
                                        jobject from, jobject to)
    {
        jboolean rv = JNI_FALSE;

        WITH_FIELD_PLATFORM_STRING(env, from, ids.path, fromPath) {
            WITH_FIELD_PLATFORM_STRING(env, to, ids.path, toPath) {
                if (rename(fromPath, toPath) == 0) {
                    rv = JNI_TRUE;
                }
            } END_PLATFORM_STRING(env, toPath);
        } END_PLATFORM_STRING(env, fromPath);
        return rv;
    }

setLastModifiedTime方法

该方法用来设置文件或目录的最后修改时间。本地方法是先获取 File 对象对应的路径,再用 stat64 函数获取指定文件或目录的属性,接着通过 st_atime 成员得到文件最后访问时间,这个时间不用改,要改的是最后修改时间,所以根据 Java 层传入的时间作为最后修改时间,最后通过 utimes 函数设置文件的最后修改时间。

    public native boolean setLastModifiedTime(File f, long time);

    JNIEXPORT jboolean JNICALL
    Java_java_io_UnixFileSystem_setLastModifiedTime(JNIEnv *env, jobject this,
                                                    jobject file, jlong time)
    {
        jboolean rv = JNI_FALSE;

        WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
            struct stat64 sb;

            if (stat64(path, &sb) == 0) {
                struct timeval tv[2];

                tv[0].tv_sec = sb.st_atime;
                tv[0].tv_usec = 0;

                tv[1].tv_sec = time / 1000;
                tv[1].tv_usec = (time % 1000) * 1000;

                if (utimes(path, tv) == 0)
                    rv = JNI_TRUE;
            }
        } END_PLATFORM_STRING(env, path);

        return rv;
    }

setReadOnly方法

该方法用于将指定文件设置成只读。本地方法逻辑是先通过 statMode 函数获取文件的属性,再去掉 S_IWUSR、S_IWGRP 和 S_IWOTH 标识,分别表示用户写权限、用户组用户写权限和非所有者和用户组用户写权限。最后通过 chmod 函数完成文件只读属性设置。

    public native boolean setReadOnly(File f);

    JNIEXPORT jboolean JNICALL
    Java_java_io_UnixFileSystem_setReadOnly(JNIEnv *env, jobject this,
                                            jobject file)
    {
        jboolean rv = JNI_FALSE;

        WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
            int mode;
            if (statMode(path, &mode)) {
                if (chmod(path, mode & ~(S_IWUSR | S_IWGRP | S_IWOTH)) >= 0) {
                    rv = JNI_TRUE;
                }
            }
        } END_PLATFORM_STRING(env, path);
        return rv;
    }

listRoots方法

该方法用于获取可用的文件系统的根文件对象的数组,对于 unix-like 来说,也就是只有一个根目录了,这之前还会用安全管理器检查下是否有根目录的权限。

    public File[] listRoots() {
            try {
                SecurityManager security = System.getSecurityManager();
                if (security != null) {
                    security.checkRead("/");
                }
                return new File[] { new File("/") };
            } catch (SecurityException x) {
                return new File[0];
            }
        }

getSpace方法

该方法用于获取所挂载的文件系统(包含该方法指定的路径)的空间大小,包括总空间大小、空闲空间大小和可用空间大小,Java 层分别用 SPACE_TOTAL = 0 SPACE_FREE = 1 SPACE_USABLE = 2标识。要查询某个文件的根目录的某某空间大小则将对应的标识传入,通过 getSpace0 本地方法获得。

可以看到本地方法使用了 statfs 或 statvfs64 函数来获取文件系统的信息,进而通过块数量乘以块大小得到总空间大小、空闲空间大小和可用空间大小。

至于为什么分别用 statfs 或 statvfs64 函数,其中 statfs 函数属于特定系统的,而 statvfs64 函数符合 POSIX 标准。一般最好优先使用 statvfs,以前的 JDK(1.7) 实现也是通过statvfs,而这里用 #ifdef MACOSX进行判断,应该是因为 MACOSX 系统对 statvfs64 函数支持不好,

    JNIEXPORT jlong JNICALL
    Java_java_io_UnixFileSystem_getSpace(JNIEnv *env, jobject this,
                                         jobject file, jint t)
    {
        jlong rv = 0L;

        WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
    #ifdef MACOSX
            struct statfs fsstat;
    #else
            struct statvfs64 fsstat;
    #endif
            memset(&fsstat, 0, sizeof(fsstat));
    #ifdef MACOSX
            if (statfs(path, &fsstat) == 0) {
                switch(t) {
                    case java_io_FileSystem_SPACE_TOTAL:
                        rv = jlong_mul(long_to_jlong(fsstat.f_bsize),
                                       long_to_jlong(fsstat.f_blocks));
                        break;
                    case java_io_FileSystem_SPACE_FREE:
                        rv = jlong_mul(long_to_jlong(fsstat.f_bsize),
                                       long_to_jlong(fsstat.f_bfree));
                        break;
                    case java_io_FileSystem_SPACE_USABLE:
                        rv = jlong_mul(long_to_jlong(fsstat.f_bsize),
                                       long_to_jlong(fsstat.f_bavail));
                        break;
                    default:
                        assert(0);
                }
            }
    #else
            if (statvfs64(path, &fsstat) == 0) {
                switch(t) {
                case java_io_FileSystem_SPACE_TOTAL:
                    rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
                                   long_to_jlong(fsstat.f_blocks));
                    break;
                case java_io_FileSystem_SPACE_FREE:
                    rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
                                   long_to_jlong(fsstat.f_bfree));
                    break;
                case java_io_FileSystem_SPACE_USABLE:
                    rv = jlong_mul(long_to_jlong(fsstat.f_frsize),
                                   long_to_jlong(fsstat.f_bavail));
                    break;
                default:
                    assert(0);
                }
            }
    #endif
        } END_PLATFORM_STRING(env, path);
        return rv;
    }

getNameMax方法

该方法用于获取系统允许的最大文件名长度,直接通过 getNameMax0 本地获取最大长度,然后判断不能超过整型的最大值。

    public int getNameMax(String path) {
            long nameMax = getNameMax0(path);
            if (nameMax > Integer.MAX_VALUE) {
                nameMax = Integer.MAX_VALUE;
            }
            return (int)nameMax;
        }

本地方法通过 pathconf 函数来获取指定路径下的文件名长度限制值,传入 _PC_NAME_MAX 即可得到。

    JNIEXPORT jlong JNICALL
    Java_java_io_UnixFileSystem_getNameMax0(JNIEnv *env, jobject this,
                                            jstring pathname)
    {
        jlong length = -1;
        WITH_PLATFORM_STRING(env, pathname, path) {
            length = (jlong)pathconf(path, _PC_NAME_MAX);
        } END_PLATFORM_STRING(env, path);
        return length != -1 ? length : (jlong)NAME_MAX;
    }

compare方法

该方法用于比较两个 File 对象,其实就是直接比较路径字符串。

     public int compare(File f1, File f2) {
            return f1.getPath().compareTo(f2.getPath());
        }

hashCode方法

该方法用于获取 File 对象的哈希值,获取 File对象路径,再将字符串变成小写,再调用字符串的 hashCode 方法,最后与 1234321 进行异或运算,得到的值即为该文件的哈希值。

    public int hashCode(File f) {
            return f.getPath().hashCode() ^ 1234321;
        }
赞(1) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » JDK不同操作系统的FileSystem(unix-like)下篇

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏