前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java:File.deleteOnExit()实现分析塈用于多级目录时的讲究

java:File.deleteOnExit()实现分析塈用于多级目录时的讲究

作者头像
10km
发布2019-05-25 22:34:35
1.3K0
发布2019-05-25 22:34:35
举报
文章被收录于专栏:10km的专栏10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433736

java.io.File类有个有意思的方法deleteOnExit,这个方法的用途简单说就是要求在java虚拟机结束的时候删除该文件/目录。

删除文件,很好理解,结束的时候这个文件自动被删除;但是对于目录,我们知道,目录是可以层层嵌套的,对于一个有多级子目录的File对象?如何确保使用deleteOnExit被准确删除呢?

还是举个栗子吧:

代码语言:javascript
复制
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方法的源码:

代码语言:javascript
复制
    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方法(代码中的中文注释为博主添加):

代码语言:javascript
复制
/*
 * %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中这段递归代码

代码语言:javascript
复制
                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在结束时会首先尝试删除父目录,但由于子目录还在,父目录不为空,所以删除失败。

解决的办法也很简单,将这两条语句调换位置就好了。

代码语言:javascript
复制
                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;
                }});
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年07月04日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档