Java NIO中File.deleteOnExit()的替代方案?

Thu*_*rge 35 java nio

Java IO具有File.deleteOnExit(),这是一种删除在JVM正常终止期间调用它的文件的方法.我发现这对于清理临时文件非常有用,特别是在单元测试期间.

但是,我没有在Java NIO的Files类中看到同名的方法.我知道我能做到path.toFile().deleteOnExit(),但我想知道是否有使用NIO的替代方案.

还有其他选择吗?如果没有,为什么没有?

Thu*_*rge 31

简答

您无法删除Java NIO中的任意文件,但是您可以StandardOpenOption.DELETE_ON_CLOSE在打开新流时使用它,这将在流关闭后立即删除该文件,方法是调用.close()(包括从try-with-resources语句)或JVM终止.例如:

Files.newOutputStream(Paths.get("Foo.tmp"), StandardOpenOption.DELETE_ON_CLOSE);
Run Code Online (Sandbox Code Playgroud)

答案很长

经过大量的挖掘,我发现Java NIO 确实有一种在退出时删除的方法,但它以与Java I/O不同的方式处理它.

首先,Javadoc for Files.createTempFile()描述了三种删除文件的方法:

在用作工作文件 [sic]的情况下,可以使用该DELETE_ON_CLOSE选项打开生成的文件,以便在调用适当的close方法时删除该文件.或者,可以使用shutdown-hookFile.deleteOnExit()机制自动删除文件.

最后一个选择File.deleteOnExit()当然是Java I/O方法,我们试图避免这种方法.当您调用上述方法时,关闭挂钩就是幕后发生的事情.但是DELETE_ON_CLOSE选项是纯Java NIO.

Java NIO假定您只对删除实际打开的文件感兴趣,而不是删除任意文件.因此,创建新流的方法,例如Files.newOutputStream()可以选择多个OpenOptions,您可以输入StandardOpenOption.DELETE_ON_CLOSE.这样做是在流关闭后立即删除文件(通过调用.close()或退出JVM).

例如:

Files.newOutputStream(Paths.get("Foo.tmp"), StandardOpenOption.DELETE_ON_CLOSE);
Run Code Online (Sandbox Code Playgroud)

...将在流关闭时删除与流关联的文件,可以是显式调用.close(),流作为try-with-resources语句的一部分关闭,也可以是JVM终止.

更新:在某些操作系统(如Linux)上,StandardOpenOption.DELETE_ON_CLOSE只要创建OutputStream就会删除.如果您只需要一个OutputStream,那可能仍然没问题.有关详细信息,请参阅DELETE_ON_CLOSE在Linux上关闭之前删除文件.

因此,Java NIO在Java I/O上添加了新功能,因为您可以在关闭流时删除文件.如果这是在JVM退出期间删除的足够好的替代方法,则可以在纯Java NIO中执行此操作.如果没有,您将不得不依赖Java I/O File.deleteOnExit()或shutdown-hook来删除文件.

  • 这是一个危险的答案。`DELETE_ON_CLOSE`不能直接替代`File.deleteOnExit()`,因为它会迫使您一直保持打开输出流的状态,只要您想使用该文件。另外,该文件实际上[在某些平台上立即删除](http://stackoverflow.com/questions/18146637/delete-on-close-deletes-files-before-close-on-linux)。 (2认同)
  • 如果不一样,那么这个问题就没有答案。 (2认同)

Blo*_*ura 13

在幕后,File.deleteOnExit()将只创建一个shutdown hook通道Runtime.addShutdownHook().

然后,你可以用NIO做同样的事情:

Runtime.getRuntime().addShutdownHook(new Thread() {
  public void run() {
    Path path = ...;

    Files.delete(path);
  }
});
Run Code Online (Sandbox Code Playgroud)

  • +1似乎在接受的答案中,关闭挂钩与`OutputStream`耦合,因此如果您打算从创建临时文件的方法返回`Path` - 您会发现您的临时文件已被删除 (2认同)

dim*_*414 6

我不会建议将鞋子StandardOpenOption.DELETE_ON_CLOSE变成替代品File.deleteOnExit().正如文档中提到的那样,它既不是通用目的,也不可能在琐碎的案例之外正常工作.

DELETE_ON_CLOSE顾名思义,它被设计用于在文件关闭后删除文件以立即清理不再需要的资源.Files.createTempFile()此处的文档同样清晰,DELETE_ON_CLOSE可用于文件打开时仅需要的"工作文件".

Files.createTempFile()文件建议或者直接写自己的关闭钩子或者干脆继续使用File.deleteOnExit().尽管您希望使用NIO,但File.deleteOnExit()如果您只使用本地文件系统,则使用本质上没有任何错误.如果您没有使用(或者不确定您正在使用)本地文件系统,因此无法使用File.deleteOnExit()它直接编写您自己的关闭挂钩,就像File这样:

public final class DeletePathsAtShutdown {
  private static LinkedHashSet<Path> files = new LinkedHashSet<>();

  static {
    Runtime.getRuntime().addShutdownHook(
        new Thread(DeletePathsAtShutdown::shutdownHook));
  }

  private static void shutdownHook() {
    LinkedHashSet<Path> local;
    synchronized {
      local = paths;
      paths = null;
    }

    ArrayList<Path> toBeDeleted = new ArrayList<>(theFiles);
    Collections.reverse(toBeDeleted);
    for (Path p : toBeDeleted) {
      try {
        Files.delete(p);
      } catch (IOException | RuntimeException e) {
        // do nothing - best-effort
      }
    }
  }

  public static synchronized void register(Path p) {
    if (paths == null) {
      throw new IllegalStateException("ShutdownHook already in progress.");
    }
    paths.add(p);
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,如果NIO包含一个开箱即用的类似关闭钩子可能会很好,但它的缺席是没有理由使用错误的工具来完成工作.您还可以添加更多功能DeletePathsAtShutdown,例如remove()功能或支持删除目录.