目录
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其他方法的源码实现。
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")));
}
这几个都是获取或者修改文件的可读可写可执行权限的,如下:
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;
}
这几个方法用于获取当前文件所在磁盘分区的可用空间,已用空间和总的空间,单位字节,其实现如下:
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());
}
//用于比较两个文件,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;
}
ExpiringCache是io包下包内可见的一个基于LinkedHashMap实现的支持自动过期删除的缓存实现,其包含的属性如下:
//元素的过期时间
private long millisUntilExpiration;
//保存元素的Map
private Map map;
//queryCount表示读写计数,get和put时都会加1,如果超过queryOverflow则会清除掉所有的过期Entry
private int queryCount;
private int queryOverflow = 300;
//最大元素个数
private int MAX_ENTRIES = 200;
其中Entry是一个内部静态类,其实现如下:
其构造方法实现如下:
重写了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 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;
}
DeleteOnExitHook也是io包包内可见的基于JVM关闭回调钩子方法实现的在JVM关闭时删除指定文件的工具类,其实现如下:
class DeleteOnExitHook {
//LinkedHashSet底层通过LinkHashMap保存元素,遍历时元素顺序是元素插入的顺序
private static LinkedHashSet 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 theFiles;
//加锁,避免被重复删除
synchronized (DeleteOnExitHook.class) {
theFiles = files;
files = null;
}
ArrayList 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>1; i 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》。