NIO.2 в Java: работа с файлами через Path и Files - Вопросы

Всего: 5 вопросов

1. 

Что такое Path и Files в NIO.2 и как создать объект Path?

NIO.2 — API пакета java.nio.file (Java 7). Интерфейс Path описывает путь к файлу или каталогу как последовательность элементов-имён, а утилитный класс Files со статическими методами выполняет операции над файлами: проверяет, читает, пишет, создаёт, копирует, перемещает, удаляет, обходит каталоги. В отличие от java.io.File, методы Files при неудаче бросают исключение с причиной (NoSuchFileException, FileAlreadyExistsException, DirectoryNotEmptyException), а не возвращают false.

  • Path.of(first, more...) (Java 11) — рекомендуемый способ: Path.of("data", "reports", "2026.txt").
  • Paths.get(...) (Java 7) — тот же результат, нужен в проектах на Java 7–10; документация предупреждает, что класс Paths может стать устаревшим.
  • Path.of(URI) — из URI со схемой file:; для jar: без открытой файловой системы — FileSystemNotFoundException.
  • file.toPath() — переход от старого File, обратно — path.toFile().

Создание Path к диску не обращается: файл может не существовать, проверяется только синтаксис (недопустимый путь даёт InvalidPathException). Части пути возвращают getFileName(), getParent(), getRoot() — все они возвращают Path и могут вернуть null (у относительного пути нет корня).

2. 

Как проверить, что два Path указывают на один и тот же файл? Чем отличаются equals(), Files.isSameFile(), toAbsolutePath() и toRealPath()?

Надёжно — через Files.isSameFile(p1, p2) или сравнение toRealPath(): оба обращаются к файловой системе. equals(), startsWith() и endsWith() у Path сравнивают пути как последовательности элементов, не читая диск, поэтому a/../b.txt и b.txt для них разные пути.

  • equals() — посимвольно по элементам; в Windows без учёта регистра (B.TXT равно b.txt), в Linux и macOS с учётом. Путь как ключ HashMap ведёт себя по-разному на разных ОС.
  • normalize() — убирает . и .. только текстово: p1.normalize().equals(p2) уже true, но символические ссылки не раскрываются.
  • toAbsolutePath() — приписывает рабочий каталог, к диску не обращается, работает для несуществующих файлов и оставляет .. как есть.
  • toRealPath() — канонический путь без .., с раскрытыми ссылками; для отсутствующего файла бросает NoSuchFileException.
  • Files.isSameFile() — читает файловую систему и отвечает, один ли это файл.

Если файла ещё нет, а нужен абсолютный путь без .., используют p.toAbsolutePath().normalize(). Та же пара normalize() + startsWith(base) защищает от выхода за пределы каталога (path traversal), когда имя файла приходит от пользователя.

3. 

Как прочитать и записать текстовый файл через Files? Что выбрать для большого файла и какая кодировка используется по умолчанию?

Небольшой файл читают целиком: Files.readString(path) (Java 11) — одной строкой, Files.readAllLines(path) — списком строк, Files.readAllBytes(path) — массивом байтов. Пишут через Files.writeString(path, text) или Files.write(path, lines); без опций файл создаётся или перезаписывается, а Files.write(path, lines) добавляет после каждой строки разделитель строк ОС. Файл открывается и закрывается внутри метода.

  • Большой файл: readString() и readAllLines() грузят всё в память (лог на гигабайты — OutOfMemoryError). Читают построчно: Files.lines(path) (Java 8, ленивый Stream<String>) или Files.newBufferedReader(path); пишут через Files.newBufferedWriter(path). Все три объекта закрывают в try-with-resources.
  • Кодировка: все текстовые методы Files без параметра Charset используют UTF-8 на любой ОС и в любой версии Java (в отличие от FileReader до Java 17).
  • Файл в другой кодировке: readString() и readAllLines() не выдают «кракозябры», а бросают MalformedInputException (например, Input length = 1 для windows-1251); у Files.lines() ошибка всплывает при обходе потока в виде UncheckedIOException. Решение — передать настоящую кодировку: Files.readString(p, Charset.forName("windows-1251")).
4. 

Какие методы Files создают, копируют, перемещают и удаляют файлы и какие исключения они бросают?

Каждый метод при неудаче бросает конкретное исключение, а не возвращает false:

  • createFile(), createDirectory() — объект уже есть: FileAlreadyExistsException; нет родительского каталога: NoSuchFileException.
  • createDirectories() — создаёт все недостающие родительские каталоги; если каталог уже есть, это не ошибка.
  • copy(src, target) — если цель существует, без StandardCopyOption.REPLACE_EXISTING будет FileAlreadyExistsException. Каталог копируется пустым, без содержимого. COPY_ATTRIBUTES переносит время изменения и права.
  • move(src, target) — переименовывает или перемещает; цель есть — FileAlreadyExistsException (без REPLACE_EXISTING); ATOMIC_MOVE выполняет перемещение одной операцией ОС, между разными дисками — AtomicMoveNotSupportedException.
  • delete(path) — удаляет файл или пустой каталог; файла нет — NoSuchFileException, каталог не пуст — DirectoryNotEmptyException.
  • deleteIfExists(path) — возвращает false, если файла не было; для непустого каталога тоже бросает DirectoryNotEmptyException.

Проверка Files.exists() перед операцией не страхует: между проверкой и действием файл может изменить другая программа (TOCTOU). Надёжнее сразу выполнять операцию и обрабатывать исключение. Временный файл Files.createTempFile(prefix, suffix) сам не удаляется — удаляйте его в finally или открывайте с DELETE_ON_CLOSE.

5. 

Чем отличаются Files.list(), Files.walk(), Files.find() и Files.newDirectoryStream()? Как удалить каталог со всем содержимым?

  • Files.list(dir) — только непосредственные элементы каталога (сам dir не входит), Stream<Path>, порядок не гарантирован; для файла вместо каталога — NotDirectoryException.
  • Files.walk(dir[, maxDepth]) — всё дерево рекурсивно; первым идёт сам dir, каталог всегда раньше своего содержимого.
  • Files.find(dir, maxDepth, (path, attrs) -> ...) — обход с фильтром, которому уже переданы атрибуты, поэтому не нужен лишний вызов Files.isRegularFile().
  • Files.newDirectoryStream(dir, "*.{java,kt}") — один уровень с фильтром по glob-шаблону, возвращает DirectoryStream<Path> для цикла for-each.

Все четыре держат каталог открытым, их закрывают через try-with-resources. По символическим ссылкам walk() и find() не переходят без FileVisitOption.FOLLOW_LINKS. Расширение в фильтре проверяют строкой: p.getFileName().toString().endsWith(".txt") — p.endsWith(".txt") не найдёт ни одного файла.

Готового метода для удаления непустого каталога нет: Files.delete() удаляет только пустой. Обходят дерево через Files.walk(root), сортируют sorted(Comparator.reverseOrder()), чтобы содержимое шло раньше своего каталога, и удаляют по одному (IOException в лямбде заворачивают в UncheckedIOException). Без reverseOrder() первым идёт сам корень, и сразу будет DirectoryNotEmptyException. Для очень больших деревьев используют Files.walkFileTree() с удалением каталога в postVisitDirectory().

Страница 1 из 1