天天看点

Java8 File / FileSystem(二) 源码解析

  目录

1、renameTo / setLastModified

2、setReadOnly / setWritable / setReadable / setExecutable / canExecute / canRead / canWrite

3、getTotalSpace / getFreeSpace / getUsableSpace

4、compareTo / equals / hashCode

5、ExpiringCache

6、DeleteOnExitHook 

     本篇博客继续上一篇《Java8 File / FileSystem(一) 源码解析》讲解File其他方法的源码实现。

1、renameTo / setLastModified

public boolean renameTo(File dest) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            //校验访问权限
            security.checkWrite(path);
            security.checkWrite(dest.path);
        }
        if (dest == null) {
            throw new NullPointerException();
        }
        //校验路径合法性
        if (this.isInvalid() || dest.isInvalid()) {
            return false;
        }
        return fs.rename(this, dest);
    }

//UnixFileSystem的实现
public boolean rename(File f1, File f2) {
        //清除路径解析的缓存
        cache.clear();
        javaHomePrefixCache.clear();
        //本地方法
        return rename0(f1, f2);
    }

public boolean setLastModified(long time) {
        if (time < 0) throw new IllegalArgumentException("Negative time");
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            //校验访问权限
            security.checkWrite(path);
        }
        if (isInvalid()) { //校验路径合法性
            return false;
        }
        //本地方法实现
        return fs.setLastModifiedTime(this, time);
    }
           

 涉及的本地方法实现都在UnixFileSystem_md.c中,其核心是用于文件重命名的rename函数和用于修改文件最后修改时间的utimes函数,如下:

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) {
            //调用rename函数,如果toPath在另一个目录下,则相当于移动文件并重命名,如果toPath已存在则rename失败
            if (rename(fromPath, toPath) == 0) {
                rv = JNI_TRUE;
            }
        } END_PLATFORM_STRING(env, toPath);
    } END_PLATFORM_STRING(env, fromPath);
    return rv;
}

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;
        //调用stat64函数获取文件属性
        if (stat64(path, &sb) == 0) {
            struct timeval tv[2];

            /* 之前的文件修改时间 */
            tv[0].tv_sec = sb.st_atime;
            tv[0].tv_usec = 0;

            /* 指定的文件修改时间,time是毫秒数 */
            tv[1].tv_sec = time / 1000;  //转换成秒数
            tv[1].tv_usec = (time % 1000) * 1000; //上述秒数对应的毫秒数
            //调用utimes函数修改文件属性
            if (utimes(path, tv) == 0)
                rv = JNI_TRUE; //修改成功,返回true
        }
    } END_PLATFORM_STRING(env, path);

    return rv;
}
           

 测试用例如下:

@Test
    public void test6() throws Exception {
        File file=new File("D:\\code\\test.txt");
        Calendar calendar=Calendar.getInstance();
        calendar.add(Calendar.HOUR,-1);
        System.out.println("setLastModified:"+file.setLastModified(calendar.getTimeInMillis()));
        System.out.println("lastModified:"+file.lastModified());
        //如果目标路径对应的文件已存在,则返回false
        System.out.println("renameTo:"+file.renameTo(new File("D:\\test2.txt")));
    }
           

2、setReadOnly / setWritable / setReadable / setExecutable / canExecute / canRead / canWrite

      这几个都是获取或者修改文件的可读可写可执行权限的,如下:

public boolean canRead() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        //本地方法实现
        return fs.checkAccess(this, FileSystem.ACCESS_READ);
    }

public boolean canWrite() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.checkAccess(this, FileSystem.ACCESS_WRITE);
    }

public boolean canExecute() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkExec(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.checkAccess(this, FileSystem.ACCESS_EXECUTE);
    }

public boolean setReadOnly() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
         //本地方法实现
        return fs.setReadOnly(this);
    }

//writable为true表示可写,为false表示不可写,ownerOnly为true,表示是否可写只针对于文件所有者,为false则适用于所有人
//如果底层的文件系统不支持对不同用户控制权限,则此参数无意义
public boolean setWritable(boolean writable, boolean ownerOnly) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
         //本地方法实现
        return fs.setPermission(this, FileSystem.ACCESS_WRITE, writable, ownerOnly);
    }

public boolean setWritable(boolean writable) {
        return setWritable(writable, true);
    }

//参数的含义同上,两个都为true,表示只有文件的所有者可以读取该文件,其他用户无法读取
public boolean setReadable(boolean readable, boolean ownerOnly) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.setPermission(this, FileSystem.ACCESS_READ, readable, ownerOnly);
    }

public boolean setReadable(boolean readable) {
        return setReadable(readable, true);
    }

/参数的含义同上,两个都为true,表示只有文件的所有者可以执行该文件,其他用户无法执行
public boolean setExecutable(boolean executable, boolean ownerOnly) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.setPermission(this, FileSystem.ACCESS_EXECUTE, executable, ownerOnly);
    }

public boolean setExecutable(boolean executable) {
        return setExecutable(executable, true);
    }

           

 涉及的本地方法实现都在UnixFileSystem_md.c中,其核心是用于查询文件是否具有指定权限的access函数和用于修改文件访问权限的chmod函数,如下:

JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_checkAccess(JNIEnv *env, jobject this,
                                        jobject file, jint a)
{
    jboolean rv = JNI_FALSE;
    int mode = 0;
    //将FileSystem定义的常量转换成C中的枚举
    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) {
        //调用access函数,获取文件的访问权限类型
        if (access(path, mode) == 0) {
            rv = JNI_TRUE;
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}


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;
        //转换成C中定义的枚举
        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);
        }
        //调用stat64获取原来的文件权限
        if (statMode(path, &mode)) {
            if (enable) //为true,表示开启对应的权限
                mode |= amode;
            else
                mode &= ~amode;
            //调用chmod修改文件权限    
            if (chmod(path, mode) >= 0) {
                rv = JNI_TRUE;
            }
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

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;
}

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;
}
           

3、getTotalSpace / getFreeSpace / getUsableSpace

      这几个方法用于获取当前文件所在磁盘分区的可用空间,已用空间和总的空间,单位字节,其实现如下:

public long getTotalSpace() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //检查访问权限
            sm.checkPermission(new RuntimePermission("getFileSystemAttributes"));
            sm.checkRead(path);
        }
        //检查路径合法
        if (isInvalid()) {
            return 0L;
        }
        //本地方法实现
        return fs.getSpace(this, FileSystem.SPACE_TOTAL);
    }

public long getFreeSpace() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("getFileSystemAttributes"));
            sm.checkRead(path);
        }
        if (isInvalid()) {
            return 0L;
        }
        return fs.getSpace(this, FileSystem.SPACE_FREE);
    }

public long getUsableSpace() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("getFileSystemAttributes"));
            sm.checkRead(path);
        }
        if (isInvalid()) {
            return 0L;
        }
        return fs.getSpace(this, FileSystem.SPACE_USABLE);
    }
           

其中本地方法实现都在UnixFileSystem_md.c中,其核心是用于获取磁盘使用情况的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) {
        struct statvfs64 fsstat;
        //将指定的内存块的值初始化成0
        memset(&fsstat, 0, sizeof(fsstat));
        //调用statvfs64读取磁盘使用情况
        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);
            }
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}
           

 测试用例如下:

@Test
    public void test7() throws Exception {
        //windows下getUsableSpace和getFreeSpace的返回值是一样的
        File file=new File("D:\\test2.txt");
        System.out.println("getUsableSpace:"+file.getUsableSpace());
        System.out.println("getTotalSpace:"+file.getTotalSpace());
        System.out.println("getFreeSpace:"+file.getFreeSpace());
    }
           

4、compareTo / equals / hashCode

//用于比较两个文件,Unix下通过文件名来比较
public int compareTo(File pathname) {
        return fs.compare(this, pathname);
    }

//基于compareTo方法判断两个文件是否一致
public boolean equals(Object obj) {
        if ((obj != null) && (obj instanceof File)) {
            return compareTo((File)obj) == 0;
        }
        return false;
    }

//Unix下基于文件名来计算hash值
public int hashCode() {
        return fs.hashCode(this);
    }

//UnixFileSystem的实现
public int compare(File f1, File f2) {
        return f1.getPath().compareTo(f2.getPath());
    }

public int hashCode(File f) {
        return f.getPath().hashCode() ^ 1234321;
    }
           

5、ExpiringCache

     ExpiringCache是io包下包内可见的一个基于LinkedHashMap实现的支持自动过期删除的缓存实现,其包含的属性如下:

//元素的过期时间
    private long millisUntilExpiration;
    //保存元素的Map
    private Map<String,Entry> map;
    //queryCount表示读写计数,get和put时都会加1,如果超过queryOverflow则会清除掉所有的过期Entry
    private int queryCount;
    private int queryOverflow = 300;
    //最大元素个数
    private int MAX_ENTRIES = 200;
           

其中Entry是一个内部静态类,其实现如下:

Java8 File / FileSystem(二) 源码解析

其构造方法实现如下:

Java8 File / FileSystem(二) 源码解析

重写了removeEldestEntry方法,该方法是在新插入一个元素时调用的,如果返回true,则会将LinkHashMap中维护的双向链表的链表头节点对应的key从Map中移除。因为是采用默认的构造函数,即双向链表中维护的是元素插入顺序而非访问顺序,所以当元素个数超过200时会移除第一个插入的元素,LinkHashMap的实现可以参考《java8 LinkedHashMap接口实现源码解析》。

其核心就是插入键值对的put方法和根据key值获取value的get方法,实现如下:

synchronized void put(String key, String val) {
        if (++queryCount >= queryOverflow) {
            //queryCount加1后,如果超过queryOverflow,则清理掉所有所有过期Entry
            cleanup();
        }
        //判断是否存在未过期的相同key的Entry
        Entry entry = entryFor(key);
        if (entry != null) {
            //如果存在则更新修改时间
            entry.setTimestamp(System.currentTimeMillis());
            entry.setVal(val);
        } else {
            //不存在则插入一个新的
            map.put(key, new Entry(System.currentTimeMillis(), val));
        }
    }

synchronized String get(String key) {
        if (++queryCount >= queryOverflow) {
            //queryCount加1后,如果超过queryOverflow,则清理掉所有所有过期Entry
            cleanup();
        }
        //查找未过期的Entry
        Entry entry = entryFor(key);
        if (entry != null) {
           //如果存在则返回val
            return entry.val();
        }
        return null;
    }

private Entry entryFor(String key) {
        Entry entry = map.get(key);
        if (entry != null) {
            //如果不为空则判断其是否过期
            long delta = System.currentTimeMillis() - entry.timestamp();
            if (delta < 0 || delta >= millisUntilExpiration) {
               //已过期则移除,返回null
                map.remove(key);
                entry = null;
            }
        }
        return entry;
    }

private void cleanup() {
        Set<String> keySet = map.keySet();
        //将keySet中的key拷贝到keys数组中,避免通过keySet避免时删除过期Entry会报并发修改异常
        String[] keys = new String[keySet.size()];
        int i = 0;
        for (String key: keySet) {
            keys[i++] = key;
        }
        for (int j = 0; j < keys.length; j++) {
            //entryFor判断key已过期则会自动移除
            entryFor(keys[j]);
        }
        queryCount = 0;
    }
           

6、DeleteOnExitHook 

     DeleteOnExitHook也是io包包内可见的基于JVM关闭回调钩子方法实现的在JVM关闭时删除指定文件的工具类,其实现如下:

class DeleteOnExitHook {
    //LinkedHashSet底层通过LinkHashMap保存元素,遍历时元素顺序是元素插入的顺序
    private static LinkedHashSet<String> files = new LinkedHashSet<>();
    static {
        //注册JVM关闭时的回调函数,也可通过Runtime.getRuntime().addShutdownHook方法添加
        sun.misc.SharedSecrets.getJavaLangAccess()
            .registerShutdownHook(2 /* Shutdown hook invocation order */,
                true /* register even if shutdown in progress */,
                new Runnable() {
                    public void run() {
                       runHooks();
                    }
                }
        );
    }

    private DeleteOnExitHook() {}

    //对外暴露的核心方法,添加需要删除的文件
    static synchronized void add(String file) {
        if(files == null) {
            // DeleteOnExitHook is running. Too late to add a file
            throw new IllegalStateException("Shutdown in progress");
        }

        files.add(file);
    }

    //执行文件删除的方法
    static void runHooks() {
        LinkedHashSet<String> theFiles;
        
        //加锁,避免被重复删除
        synchronized (DeleteOnExitHook.class) {
            theFiles = files;
            files = null;
        }

        ArrayList<String> toBeDeleted = new ArrayList<>(theFiles);

        //将List中元素的顺序反过来,即原来最后一个插入的元素遍历时是第一个
        Collections.reverse(toBeDeleted);
        for (String filename : toBeDeleted) {
            (new File(filename)).delete();
        }
    }
}

@SuppressWarnings({"rawtypes", "unchecked"})
public static void reverse(List<?> list) {
        int size = list.size();
        //REVERSE_THRESHOLD是一个常量值18,RandomAccess是一个表示支持随机访问的标记类接口
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            //mid表示List的中间位置
            for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else {
            // instead of using a raw type here, it's possible to capture
            // the wildcard but it will require a call to a supplementary
            // private method
            //ListIterator是继承自Iterator,支持元素遍历和修改
            ListIterator fwd = list.listIterator();
            //size是指定返回的第一个元素的位置,没有指定,从第一个元素开始遍历
            ListIterator rev = list.listIterator(size);
            //同上,从0开始遍历到中间位置,交换两边的元素即可
            for (int i=0, mid=list.size()>>1; i<mid; i++) {
                //获取下一个元素
                Object tmp = fwd.next();
                //previous返回size前一个元素,即最后一个元素
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }

@SuppressWarnings({"rawtypes", "unchecked"})
//将i和j对应的元素交换
public static void swap(List<?> list, int i, int j) {
        // instead of using a raw type here, it's possible to capture
        // the wildcard but it will require a call to a supplementary
        // private method
        final List l = list;
        //set方法返回替换前原来的值
        l.set(i, l.set(j, l.get(i)));
    }
           

ShutdownHook相关说明可以参考《Java关闭钩子的应用 - Shutdown Hook》。