在删除给定目录节点下的所有文件和文件夹的程序的上下文中,“try-with-resources”实际上是如何工作的

Posted

技术标签:

【中文标题】在删除给定目录节点下的所有文件和文件夹的程序的上下文中,“try-with-resources”实际上是如何工作的【英文标题】:How does "try-with-resources" actually work in the context of this program that deletes all files and folders below a given directory node 【发布时间】:2014-02-27 01:04:47 【问题描述】:

此程序删除给定节点下的所有文件和文件夹。

*编辑*

我在驱动器 K 上有以下“测试”目录结构:

Folder K:\garbage\
f_00000b (file)
f_00000f ( " )
f_0000b3 ( " )
dir1    [FOLDER]

Folder K:\garbage\dir1\
abc.pdf (file)
b12.pdf (file)
b36.pdf (file)
dir2   [FOLDER]

Folder K:\garbage\dir1\dir2\
A.pdf   (file)
a1.pdf  (file)
A2.pdf  (file)

*结束编辑*

程序之所以有效,是因为我偶然发现了“try-with-resources”,这条线被所有 ////////////////////// 包围资源”。

import java.io.IOError;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import javax.swing.JOptionPane;

public class CreatingDirectories 

    public static void main(String[] args) 

      deleteEverythingBelowThisNode(Paths.get("K:/garbage/dir1/dir2"));
    

    static void deleteAllFilesIn(String ps)

      Path p = Paths.get(ps);

      try ////////////////////////////////////////////////////////////////
         (DirectoryStream<Path> paths = Files.newDirectoryStream(p)) /////
       //////////////////////////////////////////////////////////////////

        for(Path q: paths)
          Files.delete(q);

      catch(NotDirectoryException e)
        System.out.println("Not directory: " + p.toString() + "--" + e);
      catch(DirectoryNotEmptyException e)
        System.out.println("Not empty: " + p.toString() + "--" + e);
      
      catch(IOException e)
        System.out.println("IO: " + p.toString() + "--" + e);
      
      catch(IOError e)
        System.out.println("IO ERROR: " + e);
      
    

    static void deleteEverythingBelowThisNode(Path p)

      String            sep        = p.getFileSystem().getSeparator();
      ArrayList<String> pathPieces = new ArrayList<>() ;
      String []         paths      = new String[p.getNameCount()];

      for(int i = 0 ; i < p.getNameCount() ; i++)
        pathPieces.add(p.getName(i).toString());
        paths[i] = p.getRoot().toString();
      

      for(int i = 0; i < p.getNameCount() ; i++)
        for(int k = 0; k <= i; k++)
          paths[i] += pathPieces.get(k) + sep;

      for(int k = p.getNameCount() - 1; k >= 0; k--)
        deleteAllFilesIn(paths[k]);
    

我知道“try-with-resources”是必要的:程序使用它,而不是没有它。

但我不明白为什么,也不知道它是如何解决我现在描述的原始问题的。

我最初将“资源”放在 try 块上方,就像这样,这看起来很自然:

      DirectoryStream<Path> paths = Files.newDirectoryStream(p);
      try 
           for...

除了如上所示移动那一行之外,程序结构其他方面相同,所有文件和子文件夹已成功从文件夹中删除,但DirectoryNotEmptyException 被抛出。 Windows Explorer 确认程序因异常终止后目录为空。

为什么在空目录上抛出异常?

来自the horse's mouth,“try-with-resources 语句......声明......一个对象......在语句末尾关闭。”

关闭发生在语句的末尾,所以在循环的末尾。为什么即使使用 try-with-resources 也不会发生异常?

就像现在一样,在遍历整个节点之后,它下面的所有内容都已被删除。

那么 try-with-resources 实际上做了什么来启用删除一个没有 try-with-resources 就无法删除的空文件夹?

这些看起来不像是愚蠢的问题或微不足道的情况。

DirectoryNotEmptyException 是否确实发生了,但是 try-with-resources 以某种方式处理了它?我不敢相信我在问这个问题,因为这似乎是一个愚蠢的问题,但究竟发生了什么让程序按预期运行?

【问题讨论】:

AT THE END of the statement 表示在相应的try 块的末尾。 @SotiriosDelimanolis - 并且 try 块包含一个循环,在此期间所有文件和空文件夹都将被删除,没有问题(无例外),然后关闭。但是如果没有 try-with-resources,则在循环期间会发生异常。如果仅在循环结束时关闭文件,使用 try-with-resources 如何防止异常? 你能打印堆栈跟踪吗? DirectoryNotEmptyException 扔到哪里去了? 我在这两种情况下都得到了例外。 在 sun.nio 的 sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:264) 的线程“main”java.nio.file.DirectoryNotEmptyException 中出现异常:K:\garbage\dir1。 fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:103) at java.nio.file.Files.delete(Files.java:1077) at Creatingdirectories.CreatingDirectories.deleteAllFilesIn(CreatingDirectories.java:45) at Creatingdirectories.CreatingDirectories.deleteNoMatterWhat(CreatingDirectories .java:74) 在创建目录.CreatingDirectories.main(CreatingDirectories.java:79) Java 结果:1 【参考方案1】:

在 Windows 上,您无法删除仍处于打开状态的文件或目录。 (另一方面,在 Unix 上,这不是问题 - 当您删除文件时,文件将从目录结构中删除,当您关闭文件时,文件将从磁盘中删除。但这是在 Unix 上。)

因此,如果您不使用 try-with-resources 语句来关闭目录流,那么在您尝试删除父目录中的文件并尝试删除子目录时,您仍然会打开子目录仍然打开的将失败。由于您忽略异常(您只是打印它们),因此后续删除父目录的尝试也将失败并返回 DirectoryNotEmptyException,因为您没有删除所有子目录。

您可以验证是否确实如此。当您使用try-with-resources时,请确保在删除目录中的所有文件后明确关闭目录流(使用paths.close();

这应该与 try-with-resources 块具有相同的效果(除非发生异常 - 为了保证与 try-with-resources 完全相同的行为,您需要将 paths.close(); 放入 finally 块中) .

【讨论】:

@Erwin--如果一个目录(或文件)仍然打开,Windows 不会让我删除它,这是完全有道理的。我可能会发布我自己的“答案”以显示您推荐的更改代码,但本质是:`trypaths = Files.newDirectoryStream(p); for(路径 q: 路径) ... Files.delete(q); 最后 路径.close(); `【参考方案2】:

Erwin 解释了您的问题,但您的列表末尾也有一个相当严重的问题,位于:

   for(int k = p.getNameCount() - 1; k >= 0; k--)
        deleteAllFilesIn(paths[k]);

您创建一个路径部分列表,例如,其中的部分将是:

k:/garbage/dir1/dir2 k:/garbage/dir1 k:/垃圾 k:/

这意味着您最终会尝试删除 k: 中的所有内容。它将尝试删除 k:\ 中的所有内容(所有文件;如果有任何非空子目录,它将整体失败)。

假设您只想删除最低级别的文件,您可能想要更改deleteEverythingBelowThisNode() 函数。

【讨论】:

@user1676075--代码正常。我跟踪发现只有3个节点,列表以k:/garbage结尾。也许你忽略了for( k = p.getNameCount() - 1... 中的-1。此外,我有防止失控删除的对话框。这不是一个严肃的计划;只是开心地学习目录流。【参考方案3】:

(我希望可以“回答我自己的问题”以使线程的“底线”显示最终的工作原理。这样查看线程的任何人都不必费力寻找解决方案。)

无论如何,这里是deleteAllFilesBelowThisNode 和@Erwin 的建议。

static void deleteAllFilesBelowThisNode(String ps) throws IOException

  Path p = Paths.get(ps);

  try (DirectoryStream<Path> paths = Files.newDirectoryStream(p))
  
    for(Path q: paths)
    
      if(JOptionPane.showConfirmDialog(null,"Deleting " + q.toString(),"",
           JOptionPane.OK_CANCEL_OPTION) 
        != JOptionPane.OK_OPTION)
                                  System.exit(9);
      
      Files.delete(q);
      System.out.println(q.toString() + " deleted");
    
  
  finally
    JOptionPane.showMessageDialog(null,"AHA!");
  

我添加了“啊哈!”因为我终于意识到发生了什么。

编辑

然后我取出了我昨天自豪地添加的所有内容,因为我再次发现自己像水银一样稠密。星球、金属、汽车……什么evvvvvvvvvs。

我忘记了对DeleteAllFilesBelowThisNode 的三个单独调用,这真的让我很困惑。哎呀,这是我该死的程序,但是……以林还林,等等。通过不包括整个程序,我愚弄了我。骗了我好。很好。

我真的不是白痴。

*结束编辑

但我留下了这个:

谢谢你,欧文!

另一个编辑

这是输出,便于理解:

Delete all entries beneath K:\garbage\dir1\dir2\
  deleting K:\garbage\dir1\dir2\A.pdf ... deleted
  deleting K:\garbage\dir1\dir2\a1 and 2 ver 2.pdf ... deleted
  deleting K:\garbage\dir1\dir2\A1 and 2.pdf ... deleted
K:\garbage\dir1\dir2\ closed by finally. ----- THUS DELETEABLE

Delete all entries beneath K:\garbage\dir1\
  deleting K:\garbage\dir1\abc.pdf ... deleted
  deleting K:\garbage\dir1\b12.pdf ... deleted
  deleting K:\garbage\dir1\b36.pdf ... deleted
  deleting K:\garbage\dir1\dir2 ... ***DELETED***
K:\garbage\dir1\ closed by finally. ----- THUS DELETEABLE


Delete all entries beneath K:\garbage\
  deleting K:\garbage\dir1 ... ***DELETED***
  deleting K:\garbage\f_00000b ... deleted
  deleting K:\garbage\f_00000f ... deleted
  deleting K:\garbage\f_0000b3 ... deleted
K:\garbage\ closed by finally.

这是整个程序:

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import javax.swing.JOptionPane;

public class CreatingDirectories 

  static void deleteAllFilesBelowThisNode(String ps) throws IOException

    try (DirectoryStream<Path> paths = Files.newDirectoryStream(Paths.get(ps)))
    
      for(Path q: paths)
                         System.out.print("deleting " + q.toString() + " ... ");
        if(JOptionPane.showConfirmDialog(null,"Deleting " + q.toString(),"",
             JOptionPane.OK_CANCEL_OPTION) 
          != JOptionPane.OK_OPTION)
                                                               System.exit(9);
        Files.delete(q);
                                                  System.out.println("deleted");
      
    
    finally         
      System.out.println("\n" + ps + " closed by finally.\n");
    
  
    
  static void iterativelyDeleteFoldersFromHereUpToRoot(Path p) throws IOException

    String            sep        = p.getFileSystem().getSeparator();
    ArrayList<String> pathPieces = new ArrayList<>() ;
    String []         paths      = new String[p.getNameCount()];

    for(int i = 0 ; i < p.getNameCount() ; i++)

      pathPieces.add(p.getName(i).toString());

      paths[i] = p.getRoot().toString();
    

    for(int i = 0; i < p.getNameCount() ; i++)
      for(int k = 0; k <= i; k++)
                                  paths[i] += pathPieces.get(k) + sep;

    for(int k = p.getNameCount() - 1; k >= 0; k--)

      System.out.println("\nDelete all entries beneath " + paths[k].toString());
    
      deleteAllFilesBelowThisNode(paths[k]);
    
  
    
  public static void main(String[] args) throws IOException 

    iterativelyDeleteFoldersFromHereUpToRoot(Paths.get("K:/garbage/dir1/dir2"));
  

【讨论】:

以上是关于在删除给定目录节点下的所有文件和文件夹的程序的上下文中,“try-with-resources”实际上是如何工作的的主要内容,如果未能解决你的问题,请参考以下文章

递归列出给定目录下的所有文件和目录

delphi如何删除目录和目录下的所有文件

PHP删除当前目录及其目录下的所有文件

JAVA删除某个目录及目录下的所有子目录和文件

linux怎么删除当前目录下的所有文件

cmd删除文件夹下的所有东西