目錄
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是一個内部靜态類,其實作如下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL6dGVPFTWE9EeNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5EzNzUDOycTM1IjNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
其構造方法實作如下:
重寫了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》。