Доступ к файловой системе

Для нового кода основной API — Path и Files. File остается для старых интерфейсов.

Path — описание пути

Создание Path не обращается к диску:

Path base = Path.of("data");
Path report = base.resolve("reports").resolve("daily.txt");
 
report.getFileName();  // daily.txt
report.getParent();    // data/reports
report.isAbsolute();   // false

normalize() синтаксически убирает . и .., но не разрешает symlink:

Path normalized = Path.of("a", "..", "b").normalize(); // b
Path real = existing.toRealPath(); // I/O, существование, symlink

toAbsolutePath() не делает путь каноническим и не проверяет существование.

Чтение и запись

String text = Files.readString(path, StandardCharsets.UTF_8);
Files.writeString(
        path,
        text,
        StandardCharsets.UTF_8,
        StandardOpenOption.CREATE,
        StandardOpenOption.TRUNCATE_EXISTING
);

readString/readAllBytes требуют, чтобы содержимое помещалось в память. Для больших файлов — stream/reader.

Создание, копирование, перемещение

Files.createDirectories(report.getParent());
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE);

ATOMIC_MOVE поддерживается не каждой файловой системой и может бросить AtomicMoveNotSupportedException. При этой опции остальные options игнорируются, а поведение при уже существующем target зависит от реализации. Нельзя считать сочетание ATOMIC_MOVE + REPLACE_EXISTING переносимой гарантией атомарной замены. Перемещение между разными file stores обычно не атомарно.

Files.createFile(path) атомарно проверяет отсутствие и создает файл. Проверка exists() затем создание — race condition.

Обход директории

try (Stream<Path> entries = Files.list(directory)) {
    entries.filter(Files::isRegularFile)
            .forEach(System.out::println);
}

Stream закрывают: он держит системный ресурс. Files.walk() обходит глубже и тоже требует try-with-resources.

Рекурсивное удаление

Удалять нужно детей до родителя:

Files.walkFileTree(validatedRoot, new SimpleFileVisitor<>() {
    @Override
    public FileVisitResult visitFile(
            Path file, BasicFileAttributes attrs
    ) throws IOException {
        Files.delete(file);
        return FileVisitResult.CONTINUE;
    }
 
    @Override
    public FileVisitResult postVisitDirectory(
            Path dir, IOException error
    ) throws IOException {
        if (error != null) throw error;
        Files.delete(dir);
        return FileVisitResult.CONTINUE;
    }
});

Перед такой операцией корень разрешают и валидируют явно. Нельзя принимать непроверенный /, домашний каталог или путь после подстановки. По умолчанию walkFileTree не следует по symlink; включение FOLLOW_LINKS меняет модель угроз.

File и Path

FilePath + Files
многие ошибки превращает в falseсообщает причину через exception
нет современного copy APIcopy/move/attributes/walk
классPath — interface
file.toPath()path.toFile() для совместимости

При проверке, лежит ли пользовательский путь внутри разрешенного каталога, простой normalize().startsWith(base) не защищает от symlink. Для существующих объектов сравнивают реальные пути и учитывают момент проверки/использования.

На собеседовании

  • Создает ли Path файл? Нет.
  • normalize и toRealPath? Синтаксическое упрощение против обращения к FS и разрешения symlink.
  • Почему exists + create гоняется? Состояние меняется между вызовами; нужен createFile.
  • Нужно ли закрывать Files.list/walk? Да.
  • Всегда ли ATOMIC_MOVE работает? Нет.
  • Чем Path лучше File? Полный NIO API и информативные исключения.