java删除文件夹(整个删除,Spring实现)

使用org.springframework.util.FileSystemUtils#deleteRecursively

public abstract class FilesUtils {

    public static void delFilesSpring(String filePath){

        try {
            Path path = Paths.get(filePath);
            FileSystemUtils.deleteRecursively(path);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

发现过程小记

最近在开发过程中使用了一个功能需要删除一个本地的文件夹。最开始毫不犹豫的使用了这个jdk中的这个方法java.nio.file.Files#deleteIfExists。很快写完了。

public abstract class FilesUtils {

    public static void delFilesJdk(String filePath){

        try {
            Path path = Paths.get(filePath);
            Files.deleteIfExists(path);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}


但是很不幸被无情的打脸了。测试直接报错。

java.nio.file.DirectoryNotEmptyException: D:\test
 at sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:266)
 at sun.nio.fs.AbstractFileSystemProvider.deleteIfExists(AbstractFileSystemProvider.java:108)
 at java.nio.file.Files.deleteIfExists(Files.java:1165)
 at com.hyl.light.util.FilesUtils.delFilesJdk(FilesUtils.java:31)
 at com.hyl.light.utils.FileDelTest.jdkDel(FileDelTest.java:17)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:498)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
 at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
 at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
 at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
 at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
 at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

然后又仔细看了一下源码

/**
 * Deletes a file if it exists.
 *
 * <p> As with the {@link #delete(Path) delete(Path)} method, an
 * implementation may need to examine the file to determine if the file is a
 * directory. Consequently this method may not be atomic with respect to
 * other file system operations.  If the file is a symbolic link, then the
 * symbolic link itself, not the final target of the link, is deleted.
 *
 * <p> If the file is a directory then the directory must be empty. In some
 * implementations a directory has entries for special files or links that
 * are created when the directory is created. In such implementations a
 * directory is considered empty when only the special entries exist.
 *
 * <p> On some operating systems it may not be possible to remove a file when
 * it is open and in use by this Java virtual machine or other programs.
 *
 * @param   path
 *          the path to the file to delete
 *
 * @return  {@code true} if the file was deleted by this method; {@code
 *          false} if the file could not be deleted because it did not
 *          exist
 *
 * @throws  DirectoryNotEmptyException
 *          if the file is a directory and could not otherwise be deleted
 *          because the directory is not empty <i>(optional specific
 *          exception)</i>
 * @throws  IOException
 *          if an I/O error occurs
 * @throws  SecurityException
 *          In the case of the default provider, and a security manager is
 *          installed, the {@link SecurityManager#checkDelete(String)} method
 *          is invoked to check delete access to the file.
 */
public static boolean deleteIfExists(Path path) throws IOException {
    return provider(path).deleteIfExists(path);
}

源码很明显的指出了这里会抛出一个异常DirectoryNotEmptyException。大概意思就是如果是文件夹,同时文件夹内不是空的就会抛出这个异常。
GG。问题想的太简单了。
网上搜索了一波,想想有没有大佬已经遇见过类似的问题了。果然一波搜索就出来。大概的思路就是递归去查找文件夹下的目录一一删除。具体的代码网上有很多。不做说明了。
但是不死心的我,同时作为spring的脑残粉,我的直觉告诉我spring中肯定也有同样的问题。他们是怎么解决的呢?很开心,也很容易,我在spring找到了。
org.springframework.util.FileSystemUtils#deleteRecursively

/**
 * Delete the supplied {@link File} - for directories,
 * recursively delete any nested directories or files as well.
 * @param root the root {@code File} to delete
 * @return {@code true} if the {@code File} existed and was deleted,
 * or {@code false} it it did not exist
 * @throws IOException in the case of I/O errors
 * @since 5.0
 */
public static boolean deleteRecursively(@Nullable Path root) throws IOException {
   if (root == null) {
      return false;
   }
   if (!Files.exists(root)) {
      return false;
   }

   Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
         Files.delete(file);
         return FileVisitResult.CONTINUE;
      }
      @Override
      public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
         Files.delete(dir);
         return FileVisitResult.CONTINUE;
      }
   });
   return true;
}

这里spring直接使用了jdk的Files.walkFileTree同时重写了SimpleFileVisitor的两个方法visitFilepostVisitDirectory
原来是这样的:

/**
 * A simple visitor of files with default behavior to visit all files and to
 * re-throw I/O errors.
 *
 * <p> Methods in this class may be overridden subject to their general contract.
 *
 * @param   <T>     The type of reference to the files
 *
 * @since 1.7
 */

public class SimpleFileVisitor<T> implements FileVisitor<T> {
    /**
     * Initializes a new instance of this class.
     */
    protected SimpleFileVisitor() {
    }

    /**
     * Invoked for a directory before entries in the directory are visited.
     *
     * <p> Unless overridden, this method returns {@link FileVisitResult#CONTINUE
     * CONTINUE}.
     */
    @Override
    public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
        throws IOException
    {
        Objects.requireNonNull(dir);
        Objects.requireNonNull(attrs);
        return FileVisitResult.CONTINUE;
    }

    /**
     * Invoked for a file in a directory.
     *
     * <p> Unless overridden, this method returns {@link FileVisitResult#CONTINUE
     * CONTINUE}.
     */
    @Override
    public FileVisitResult visitFile(T file, BasicFileAttributes attrs)
        throws IOException
    {
        Objects.requireNonNull(file);
        Objects.requireNonNull(attrs);
        return FileVisitResult.CONTINUE;
    }

    /**
     * Invoked for a file that could not be visited.
     *
     * <p> Unless overridden, this method re-throws the I/O exception that prevented
     * the file from being visited.
     */
    @Override
    public FileVisitResult visitFileFailed(T file, IOException exc)
        throws IOException
    {
        Objects.requireNonNull(file);
        throw exc;
    }

    /**
     * Invoked for a directory after entries in the directory, and all of their
     * descendants, have been visited.
     *
     * <p> Unless overridden, this method returns {@link FileVisitResult#CONTINUE
     * CONTINUE} if the directory iteration completes without an I/O exception;
     * otherwise this method re-throws the I/O exception that caused the iteration
     * of the directory to terminate prematurely.
     */
    @Override
    public FileVisitResult postVisitDirectory(T dir, IOException exc)
        throws IOException
    {
        Objects.requireNonNull(dir);
        if (exc != null)
            throw exc;
        return FileVisitResult.CONTINUE;
    }
}

原始的并没有任何操作。但是spring中的工具类重写过后提供了delete操作。

/**
 * Walks a file tree.
 *
 * <p> This method works as if invoking it were equivalent to evaluating the
 * expression:
 * <blockquote><pre>
 * walkFileTree(start, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, visitor)
 * </pre></blockquote>
 * In other words, it does not follow symbolic links, and visits all levels
 * of the file tree.
 *
 * @param   start
 *          the starting file
 * @param   visitor
 *          the file visitor to invoke for each file
 *
 * @return  the starting file
 *
 * @throws  SecurityException
 *          If the security manager denies access to the starting file.
 *          In the case of the default provider, the {@link
 *          SecurityManager#checkRead(String) checkRead} method is invoked
 *          to check read access to the directory.
 * @throws  IOException
 *          if an I/O error is thrown by a visitor method
 */
public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor)
    throws IOException
{
    return walkFileTree(start,
                        EnumSet.noneOf(FileVisitOption.class),
                        Integer.MAX_VALUE,
                        visitor);
}
/**
 * Walks a file tree.
 *
 * <p> This method walks a file tree rooted at a given starting file. The
 * file tree traversal is <em>depth-first</em> with the given {@link
 * FileVisitor} invoked for each file encountered. File tree traversal
 * completes when all accessible files in the tree have been visited, or a
 * visit method returns a result of {@link FileVisitResult#TERMINATE
 * TERMINATE}. Where a visit method terminates due an {@code IOException},
 * an uncaught error, or runtime exception, then the traversal is terminated
 * and the error or exception is propagated to the caller of this method.
 *
 * <p> For each file encountered this method attempts to read its {@link
 * java.nio.file.attribute.BasicFileAttributes}. If the file is not a
 * directory then the {@link FileVisitor#visitFile visitFile} method is
 * invoked with the file attributes. If the file attributes cannot be read,
 * due to an I/O exception, then the {@link FileVisitor#visitFileFailed
 * visitFileFailed} method is invoked with the I/O exception.
 *
 * <p> Where the file is a directory, and the directory could not be opened,
 * then the {@code visitFileFailed} method is invoked with the I/O exception,
 * after which, the file tree walk continues, by default, at the next
 * <em>sibling</em> of the directory.
 *
 * <p> Where the directory is opened successfully, then the entries in the
 * directory, and their <em>descendants</em> are visited. When all entries
 * have been visited, or an I/O error occurs during iteration of the
 * directory, then the directory is closed and the visitor's {@link
 * FileVisitor#postVisitDirectory postVisitDirectory} method is invoked.
 * The file tree walk then continues, by default, at the next <em>sibling</em>
 * of the directory.
 *
 * <p> By default, symbolic links are not automatically followed by this
 * method. If the {@code options} parameter contains the {@link
 * FileVisitOption#FOLLOW_LINKS FOLLOW_LINKS} option then symbolic links are
 * followed. When following links, and the attributes of the target cannot
 * be read, then this method attempts to get the {@code BasicFileAttributes}
 * of the link. If they can be read then the {@code visitFile} method is
 * invoked with the attributes of the link (otherwise the {@code visitFileFailed}
 * method is invoked as specified above).
 *
 * <p> If the {@code options} parameter contains the {@link
 * FileVisitOption#FOLLOW_LINKS FOLLOW_LINKS} option then this method keeps
 * track of directories visited so that cycles can be detected. A cycle
 * arises when there is an entry in a directory that is an ancestor of the
 * directory. Cycle detection is done by recording the {@link
 * java.nio.file.attribute.BasicFileAttributes#fileKey file-key} of directories,
 * or if file keys are not available, by invoking the {@link #isSameFile
 * isSameFile} method to test if a directory is the same file as an
 * ancestor. When a cycle is detected it is treated as an I/O error, and the
 * {@link FileVisitor#visitFileFailed visitFileFailed} method is invoked with
 * an instance of {@link FileSystemLoopException}.
 *
 * <p> The {@code maxDepth} parameter is the maximum number of levels of
 * directories to visit. A value of {@code 0} means that only the starting
 * file is visited, unless denied by the security manager. A value of
 * {@link Integer#MAX_VALUE MAX_VALUE} may be used to indicate that all
 * levels should be visited. The {@code visitFile} method is invoked for all
 * files, including directories, encountered at {@code maxDepth}, unless the
 * basic file attributes cannot be read, in which case the {@code
 * visitFileFailed} method is invoked.
 *
 * <p> If a visitor returns a result of {@code null} then {@code
 * NullPointerException} is thrown.
 *
 * <p> When a security manager is installed and it denies access to a file
 * (or directory), then it is ignored and the visitor is not invoked for
 * that file (or directory).
 *
 * @param   start
 *          the starting file
 * @param   options
 *          options to configure the traversal
 * @param   maxDepth
 *          the maximum number of directory levels to visit
 * @param   visitor
 *          the file visitor to invoke for each file
 *
 * @return  the starting file
 *
 * @throws  IllegalArgumentException
 *          if the {@code maxDepth} parameter is negative
 * @throws  SecurityException
 *          If the security manager denies access to the starting file.
 *          In the case of the default provider, the {@link
 *          SecurityManager#checkRead(String) checkRead} method is invoked
 *          to check read access to the directory.
 * @throws  IOException
 *          if an I/O error is thrown by a visitor method
 */
public static Path walkFileTree(Path start,
                                Set<FileVisitOption> options,
                                int maxDepth,
                                FileVisitor<? super Path> visitor)
    throws IOException
{
    /**
     * Create a FileTreeWalker to walk the file tree, invoking the visitor
     * for each event.
     */
    try (FileTreeWalker walker = new FileTreeWalker(options, maxDepth)) {
        FileTreeWalker.Event ev = walker.walk(start);
        do {
            FileVisitResult result;
            switch (ev.type()) {
                case ENTRY :
                    IOException ioe = ev.ioeException();
                    if (ioe == null) {
                        assert ev.attributes() != null;
                        result = visitor.visitFile(ev.file(), ev.attributes());
                    } else {
                        result = visitor.visitFileFailed(ev.file(), ioe);
                    }
                    break;

                case START_DIRECTORY :
                    result = visitor.preVisitDirectory(ev.file(), ev.attributes());

                    // if SKIP_SIBLINGS and SKIP_SUBTREE is returned then
                    // there shouldn't be any more events for the current
                    // directory.
                    if (result == FileVisitResult.SKIP_SUBTREE ||
                        result == FileVisitResult.SKIP_SIBLINGS)
                        walker.pop();
                    break;

                case END_DIRECTORY :
                    result = visitor.postVisitDirectory(ev.file(), ev.ioeException());

                    // SKIP_SIBLINGS is a no-op for postVisitDirectory
                    if (result == FileVisitResult.SKIP_SIBLINGS)
                        result = FileVisitResult.CONTINUE;
                    break;

                default :
                    throw new AssertionError("Should not get here");
            }

            if (Objects.requireNonNull(result) != FileVisitResult.CONTINUE) {
                if (result == FileVisitResult.TERMINATE) {
                    break;
                } else if (result == FileVisitResult.SKIP_SIBLINGS) {
                    walker.skipRemainingSiblings();
                }
            }
            ev = walker.next();
        } while (ev != null);
    }

    return start;
}

这样Files#walkFileTree就拥有了删除功能。就可以使用jdk中的这个方法去做文件的查找和删除。

迅速另外写了一个方法测试一波。

public abstract class FilesUtils {

    public static void delFilesSpring(String filePath){

        try {
            Path path = Paths.get(filePath);
            FileSystemUtils.deleteRecursively(path);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    
}


public class FileDelTest {

    String filePath = "D:\\test";

   @Test
    public void springDel(){
        FilesUtils.delFilesSpring(filePath);
    }

}

测试通过完美。果然‘spring’大佬还是很专业的。

ps:我使用的是Spring-5.1.6.RELEASE版本。

补充一个 RocketMQ 中的做法,递归删除。
org.apache.rocketmq.common.UtilAll#deleteFile

   public static void deleteFile(File file) {
        if (!file.exists()) {
            return;
        }
        if (file.isFile()) {
            file.delete();
        } else if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File file1 : files) {
                deleteFile(file1);
            }
            file.delete();
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值