【问题标题】:Alternative to File.deleteOnExit() in Java NIO?Java NIO 中 File.deleteOnExit() 的替代方案?
【发布时间】:2015-04-29 09:27:18
【问题描述】:

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

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

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

【问题讨论】:

  • 蔚来版本会做什么不同或更好的? (除了从调用链中删除.toFile()。)
  • @Thunderforge DataInputStream.readLine() 实际上是 1998 年的 @Deprecated,它仍然存在。 “完全住在蔚来”有什么好处?
  • @PeterLawrey 我知道 Java 往往不会删除他们不赞成使用的东西(这对我来说来自 Python 的感觉很奇怪),但他们总有一天会这样做。至于“完全生活在 NIO 中”,如果代码完全在 NIO 中而不是两者都使用,代码会更容易掌握(特别是对于可能已经开始使用 NIO 并且以前从未使用过 IO 的年轻开发人员)。不管你是否同意这个理由,我仍然想知道是否有替代方案。
  • @Thunderforge 我想说的是,在 Java 中,除非有令人信服的理由,否则它们会避免添加东西。他们添加的每一种方法都经过仔细考虑,如果它所做的一切与现有方法大致相同,我怀疑它不会发生。
  • “完全生活在 NIO 中”并不是什么好处,它只是一个任意的自我强加的约束。

标签: java nio


【解决方案1】:

简答

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

Files.newOutputStream(Paths.get("Foo.tmp"), StandardOpenOption.DELETE_ON_CLOSE);

长答案

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

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

如果用作工作文件 [原文如此],结果文件可能会被打开 使用DELETE_ON_CLOSE 选项,以便在 调用适当的关闭方法。或者,shutdown-hook, 或File.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);

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

更新:在某些操作系统(例如 Linux)上,StandardOpenOption.DELETE_ON_CLOSE 在创建 OutputStream 后立即删除。如果您只需要一个 OutputStream,那可能还是可以的。请参阅DELETE_ON_CLOSE deletes files before close on Linux 了解更多信息。

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

【讨论】:

  • 这是一个危险的答案; DELETE_ON_CLOSE 不是File.deleteOnExit() 的直接替代品,因为只要您想使用该文件,它就会强制您保持输出流打开。另外,该文件实际上是deleted immediately on some platforms
  • @megaflop 我在回答中指出DELETE_ON_CLOSE 的工作方式不同,因为它会“在流关闭后立即删除文件”,所以我认为我已经明确表示它不会与File.deleteOnExit() 的工作方式不同。如果您可以立即删除它(在我的用例中,我是这样),那么这是一个很好的替代品。
  • 如果不一样,则不回答问题。
【解决方案2】:

我不建议将StandardOpenOption.DELETE_ON_CLOSE 换成File.deleteOnExit()。正如文档中提到的那样,它既不是通用的,也不太可能在琐碎的情况下正常工作。

DELETE_ON_CLOSE,顾名思义,就是designed to be used to remove a file once it's closed,用于立即清理不再需要的资源。 Files.createTempFile() 的文档在这一点上同样清楚,DELETE_ON_CLOSE 可用于仅在文件打开时需要的“工作文件”。

Files.createTempFile() 文档建议直接编写自己的关闭挂钩或继续使用File.deleteOnExit()。尽管您希望使用 NIO,但如果您只使用本地文件系统,使用 File.deleteOnExit() 本质上并没有错。如果您没有使用(或不确定您正在使用)本地文件系统,因此不能使用File.deleteOnExit(),那么编写自己的关闭挂钩just like what File does 很简单:

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);
  }
}

当然,如果 NIO 包含一个类似的开箱即用的关闭挂钩可能会很好,但它的缺失并不是使用错误工具来完成这项工作的理由。您还可以向DeletePathsAtShutdown 添加更多功能,例如remove() 函数,或支持deleting directories

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • +1 在接受的答案中,关闭挂钩似乎与OutputStream 耦合,因此如果您打算从创建临时文件的方法返回Path - 您会发现您的临时文件已被删除
    猜你喜欢
    • 2012-07-26
    • 2013-03-10
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-28
    • 1970-01-01
    相关资源
    最近更新 更多