Подтвердить что ты не робот

Альтернатива File.deleteOnExit() в Java NIO?

Java IO имеет File.deleteOnExit(), который является методом, который удаляет файл, который он вызывается при нормальном завершении JVM. Я нашел, что это очень полезно для очистки временных файлов, особенно во время модульных тестов.

Однако я не вижу метод с тем же именем в классе Java NIO Files. Я знаю, что могу сделать path.toFile().deleteOnExit(), но я хотел бы знать, есть ли альтернатива, использующая NIO.

Есть ли альтернатива? Если нет, то почему этого не происходит?

4b9b3361

Ответ 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.

Во-первых, Javadoc для Files.createTempFile() описывает три способа удаления файлов:

При использовании в качестве рабочих файлов [sic] результирующий файл может быть открыт используя параметр DELETE_ON_CLOSE, чтобы файл был удален, когда вызывается соответствующий метод close. Кроме того, shutdown-hook, или File.deleteOnExit() механизм может использоваться для удаления файла автоматически.

Последний выбор File.deleteOnExit() - это, конечно, метод ввода-вывода Java, который мы пытаемся избежать. Завершение работы - это то, что происходит за кулисами, когда вы вызываете вышеупомянутый метод. Но параметр 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 удаляет файлы перед закрытием в Linux для получения дополнительной информации.

Итак, Java NIO добавляет новые функции по сравнению с Java I/O тем, что вы можете удалить файл при закрытии потока. Если это достаточно хорошая альтернатива удалению во время выхода JVM, вы можете сделать это в чистой Java NIO. Если нет, вам придется полагаться на Java I/O File.deleteOnExit() или на выключение, чтобы удалить файл.

Ответ 2

За кулисами File.deleteOnExit() будет просто создавать shutdown hook через Runtime.addShutdownHook().

Затем вы можете сделать то же самое с NIO:

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

    Files.delete(path);
  }
});

Ответ 3

Я бы не предложил shoe-horning StandardOpenOption.DELETE_ON_CLOSE заменить на File.deleteOnExit(). Поскольку в документации упоминается, что она не предназначена для общего назначения и не может работать правильно за пределами тривиальных случаев.

DELETE_ON_CLOSE, как следует из названия, является предназначенным для удаления файла после его закрытия, чтобы немедленно очистить ненужный ресурс. Документация для Files.createTempFile() аналогично понятна в этой точке, DELETE_ON_CLOSE может использоваться для "рабочих файлов", только когда файл открыто.

Документы Files.createTempFile() предлагают прямо или записывать собственный крюк остановки или просто продолжать использовать File.deleteOnExit(). Несмотря на ваше желание использовать NIO, нет ничего принципиального в использовании File.deleteOnExit(), если вы работаете только с локальной файловой системой. Если вы не используете (или не уверены, что используете) локальную файловую систему и, следовательно, не можете использовать File.deleteOnExit() достаточно просто, чтобы написать свой собственный выключение hook точно так же, как 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);
  }
}

Конечно, было бы неплохо, если бы NIO включил аналогичный выключение из коробки, но его отсутствие не является основанием для использования неправильного инструмента для работы. Вы также можете добавить дополнительные функции в DeletePathsAtShutdown, например, функцию remove() или поддержку удаления каталогов.