目錄
2、setReadOnly / setWritable / setReadable / setExecutable / canExecute / canRead / canWrite
3、getTotalSpace / getFreeSpace / getUsableSpace
4、compareTo / equals / hashCode
本篇博客繼續上一篇《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是一個內部靜態類,其實現如下:
其構造方法實現如下:
重寫了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》。