java:File.deleteOnExit()实现分析塈用于多级目录时的讲究
2016-07-04 18:13
447 查看
java.io.File类有个有意思的方法
deleteOnExit,这个方法的用途简单说就是要求在java虚拟机结束的时候删除该文件/目录。
删除文件,很好理解,结束的时候这个文件自动被删除;但是对于目录,我们知道,目录是可以层层嵌套的,对于一个有多级子目录的File对象?如何确保使用
deleteOnExit被准确删除呢?
还是举个栗子吧:
package net.facesdk.cas; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class CopyUtils{ /** * NIO方式复制文件<br> * 目标文件所在的文件夹如果不存在自动创建文件夹 * @param src 源文件 * @param dst 目标文件 * @throws IOException */ public static void nioCopyFile(File src,File dst) throws IOException { if(null==src||null==dst) throw new NullPointerException("src or dst is null"); if(!src.exists()||!src.isFile()) throw new IllegalArgumentException(String.format("INVALID FIILE NAME(无效文件名) src=%s",src.getCanonicalPath())); if (dst.exists() &&!dst.isFile()) { throw new IllegalArgumentException(String.format("INVALID FIILE NAME(无效文件名) dst=%s",dst.getCanonicalPath())); } File folder = dst.getParentFile(); if (!folder.exists()) folder.mkdirs(); if(((src.length()+(1<<10)-1)>>10)>(folder.getFreeSpace()>>10)) throw new IOException(String.format("DISK ALMOST FULL(磁盘空间不足) %s",folder.getCanonicalPath())); System.out.printf("src=%s,to=%s\n", src.getAbsolutePath(),dst.getAbsolutePath()); FileInputStream fin=new FileInputStream(src); FileOutputStream fout = new FileOutputStream(dst); FileChannel fic = fin.getChannel(); FileChannel foc = fout.getChannel(); try { ByteBuffer bb = ByteBuffer.allocate((int) fic.size()); fic.read(bb); foc.write(bb); } finally { fic.close(); foc.close(); fin.close(); fout.close(); } } /** * 递归复制文件/文件夹到指定的文件夹,并且在JVM结束时删除 * @param src 原文件/文件夹 * @param dstFolder 目标文件夹 */ public static final void copyAndDeleteOnExit(final File src,final File dstFolder) { if(src.isDirectory()){ src.listFiles(new FileFilter(){ @Override public boolean accept(File pathname) { System.out.printf("pathname=%s\n", pathname.getName()); File df = new File(dstFolder,src.getName()); copyAndDeleteOnExit(pathname,df); df.deleteOnExit();// JVM结束时删除指定文件夹在 return false; }}); }else{ try { File dst=new File(dstFolder,src.getName()); nioCopyFile(src,dst); // JVM结束时删除文件 dst.deleteOnExit(); } catch (IOException e) { e.printStackTrace(); } } } }
上面这段代码中的
copyAndDeleteOnExit方法的作用是复制一个文件或文件夹的所有文件到指定的文件夹下(如果是文件夹,则递归调用),并在JVM结束时自动删除所有这些复制文件。
但当我实际运行时,发现包含子目录的文件夹,在JVM结束时并没被删除,该文件夹下所有的子目录都没有被删除,而子目录下的文件都被删除了。
这是为什么呢?仔细研究了
copyAndDeleteOnExit方法的说明。找到了原因,见下面红框标出的部分
这是红线标出的是什么意思呢?我们继续查看
deleteOnExit方法的源码:
public void deleteOnExit() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkDelete(path); } DeleteOnExitHook.add(path);//将文件路径向DeleteOnExitHook类注册 }
每调用一次
copyAndDeleteOnExit方法,其实将该File的路径加到JVM内部由
java.io.DeleteOnExitHook类维护的一张表中,在JVM结束时会根据这张表倒序删除表中的文件。
下面是
java.io.DeleteOnExitHook类的源码,非常简单,JVM结束时删除文件就是调用
DeleteOnExitHook类中的
runHooks方法(代码中的中文注释为博主添加):
/* * %W% %E% * * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package java.io; import java.util.*; import java.io.File; /** * This class holds a set of filenames to be deleted on VM exit through a shutdown hook. * A set is used both to prevent double-insertion of the same file as well as offer * quick removal. */ class DeleteOnExitHook { static { sun.misc.SharedSecrets.getJavaLangAccess() .registerShutdownHook(2 /* Shutdown hook invocation order */, new Runnable() { public void run() { runHooks(); } }); } // 保存待删除文件名的哈希表 private static LinkedHashSet<String> files = new LinkedHashSet<String>(); private DeleteOnExitHook() {} // 将一个文件路径字符串注册(添加)到待删除列表中 files中, // 因为 files 是java.util.LinkedHashSet类型的哈希表,没有重复数据, // 所以重复添加无效 static synchronized void add(String file) { if(files == null) throw new IllegalStateException("Shutdown in progress"); files.add(file); } //JVM结束时删除文件调用的方法 static void runHooks() { LinkedHashSet<String> theFiles; synchronized (DeleteOnExitHook.class) { theFiles = files; files = null; } ArrayList<String> toBeDeleted = new ArrayList<String>(theFiles); // reverse the list to maintain previous jdk deletion order. // Last in first deleted. Collections.reverse(toBeDeleted);//在这里将待删除文件列表反序了 for (String filename : toBeDeleted) { (new File(filename)).delete();// 顺序删除文件 } } }
看到这里,终于搞清楚了:
JVM在删除这些被
deleteOnExit指定的文件/文件夹的时候,是按调用
deleteOnExit方法的相反的顺序进行的。也就是说最后调用
deleteOnExit的File最先被删除。
我们再回头看看
copyAndDeleteOnExit中这段递归代码
public boolean accept(File pathname) { System.out.printf("pathname=%s\n", pathname.getName()); File df = new File(dstFolder,src.getName()); copyAndDeleteOnExit(pathname,df); df.deleteOnExit();// JVM结束时删除指定文件夹 return false; }});
显然,因为
df.deleteOnExit();在
copyAndDeleteOnExit(pathname,df);递归调用语句之后,
所以父目录是在子目录之后调用
deleteOnExit方法的,那么JVM在结束时会首先尝试删除父目录,但由于子目录还在,父目录不为空,所以删除失败。
解决的办法也很简单,将这两条语句调换位置就好了。
public boolean accept(File pathname) { System.out.printf("pathname=%s\n", pathname.getName()); File df = new File(dstFolder,src.getName()); df.deleteOnExit();// JVM结束时删除指定文件夹 copyAndDeleteOnExit(pathname,df); return false; }});
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树