JavaRush /Курсы /JAVA 25 SELF /NIO2: Files, Paths, Files.walk: обход файловой системы

NIO2: Files, Paths, Files.walk: обход файловой системы

JAVA 25 SELF
39 уровень , 0 лекция
Открыта

1. NIO2: знакомимся подробнее

Мы с вами уже успели познакомиться с NIO2, но сейчас повторим и углубим знания об этой полезной библиотеке для работы с файлами и директориями.

Раньше в Java был только класс File. Он умел проверять существование файла, создавать и удалять файлы и папки, получать список файлов в директории. Но у него было много ограничений:

  • неудобно работать с путями, особенно если нужно учитывать разные операционные системы (C:\Users\user\file.txt на Windows и /home/user/file.txt на Linux);
  • отсутствует нормальная поддержка символьных ссылок, прав доступа и атрибутов файлов;
  • возможности обхода дерева каталогов ограничены;
  • обработка ошибок оставляла желать лучшего.

С появлением NIO2 (New Input/Output, версия 2) в Java 7 жизнь разработчика стала проще и приятнее. Теперь в распоряжении есть:

  • класс Path для удобной работы с путями к файлам и папкам;
  • класс Files, который обеспечивает все основные операции: чтение, запись, копирование, удаление, получение информации о файлах;
  • интерфейс FileVisitor и методы вроде Files.walk, которые позволяют легко и гибко обходить файловую систему.

Почему это важно?

  • Кроссплатформенность: Один и тот же код работает на Windows, Linux, macOS, не думая о разделителях (/ или \).
  • Безопасность и удобство: Больше информации об ошибках, меньше магии и неожиданных сюрпризов.
  • Мощность: Можно обрабатывать огромные директории и даже делать обход рекурсивно с фильтрацией и параллельной обработкой.

2. Основные классы: Path и Files

Класс Path

Path — это современное представление пути к файлу или папке. Он не обязательно указывает на реально существующий файл — просто путь, с которым удобно работать.

Получение Path

import java.nio.file.Path;
import java.nio.file.Paths;

Path path1 = Paths.get("file.txt"); // относительный путь
Path path2 = Paths.get("/home/user/file.txt"); // абсолютный путь
Path path3 = Path.of("mydir", "subdir", "file.txt"); // с Java 11+

Факт: Path не зависит от операционной системы. Забудьте про ручное склеивание строк с / или \!

Преобразование в строку

System.out.println(path1.toString());

Получение родительского каталога и имени файла

Path parent = path1.getParent(); // может быть null для относительных путей
Path fileName = path1.getFileName(); // только имя файла

Класс Files

Files — это сборник статических методов для всех операций с файлами и директориями:

  • Проверка существования: Files.exists(path)
  • Чтение и запись файлов: Files.readAllBytes(path), Files.write(path, bytes)
  • Получение информации: Files.size(path), Files.getLastModifiedTime(path)
  • Копирование, удаление, перемещение: Files.copy, Files.delete, Files.move

Примеры:

import java.nio.file.Files;
import java.nio.file.Path;

Path path = Path.of("file.txt");
if (Files.exists(path)) {
    System.out.println("Файл существует!");
    System.out.println("Размер: " + Files.size(path) + " байт");
    System.out.println("Последнее изменение: " + Files.getLastModifiedTime(path));
} else {
    System.out.println("Файл не найден.");
}

3. Обход файловой системы: Files.walk и друзья

Проблема старого способа

В старом API, чтобы обойти все файлы в папке и её подпапках, приходилось писать рекурсивные функции, вручную проверять, где файл, а где папка, и следить, чтобы не попасть в бесконечную рекурсию. Это было не только утомительно, но и очень легко ошибиться.

Современный способ: Files.walk

Files.walk(Path start) возвращает Stream<Path> — поток всех файлов и папок, начиная с указанного пути, включая все подкаталоги. Теперь обход файловой системы — это просто работа с потоками!

Пример: Вывести все файлы и папки

import java.nio.file.*;

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.forEach(System.out::println);
}

Здесь будут выведены все пути: и файлы, и папки, начиная с mydir.

Пример: Только файлы (без папок)

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.filter(Files::isRegularFile)
         .forEach(System.out::println);
}

Метод Files.isRegularFile(path) вернёт true только для обычных файлов (не папок, не симлинков).

Пример: Поиск файлов по расширению

Допустим, нам нужно найти все .txt-файлы в каталоге и подкаталогах:

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.filter(Files::isRegularFile)
         .filter(path -> path.toString().endsWith(".txt"))
         .forEach(System.out::println);
}

Пример: Подсчёт общего размера всех файлов

long totalSize = 0;
try (var paths = Files.walk(Path.of("mydir"))) {
    totalSize = paths.filter(Files::isRegularFile)
                     .mapToLong(path -> {
                         try {
                             return Files.size(path);
                         } catch (Exception e) {
                             System.err.println("Ошибка чтения размера: " + path);
                             return 0L;
                         }
                     })
                     .sum();
}
System.out.println("Общий размер файлов: " + totalSize + " байт");

Важно!

  • Метод Files.walk возвращает поток, который нужно закрывать (он реализует AutoCloseable). Поэтому используем try-with-resources!
  • По умолчанию обход глубины — до самого дна (все подкаталоги). Можно ограничить глубину: Files.walk(path, maxDepth)

4. Практические задачи

Задача 1: Найти все картинки в каталоге

Нужно найти все файлы с расширением .jpg, .png, .gif в папке images и вывести их имена.

import java.nio.file.*;
import java.util.Set;

Set<String> extensions = Set.of(".jpg", ".png", ".gif");

try (var paths = Files.walk(Path.of("images"))) {
    paths.filter(Files::isRegularFile)
         .filter(path -> {
             String name = path.getFileName().toString().toLowerCase();
             return extensions.stream().anyMatch(name::endsWith);
         })
         .forEach(System.out::println);
}

Задача 2: Скопировать все .txt-файлы в другую папку

import java.nio.file.*;

Path sourceDir = Path.of("src");
Path destDir = Path.of("dest");

try (var paths = Files.walk(sourceDir)) {
    paths.filter(Files::isRegularFile)
         .filter(path -> path.toString().endsWith(".txt"))
         .forEach(path -> {
             try {
                 Path relative = sourceDir.relativize(path);
                 Path target = destDir.resolve(relative);
                 Files.createDirectories(target.getParent());
                 Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);
                 System.out.println("Скопирован: " + path + " -> " + target);
             } catch (Exception e) {
                 System.err.println("Ошибка копирования: " + path);
             }
         });
}

Здесь мы сохраняем структуру поддиректорий.

5. Полезные нюансы

Преимущества NIO2

Кроссплатформенность

Path сам разбирается с разделителями папок. Ваш код будет работать одинаково на Windows, Linux, macOS.

Потоковая обработка

Методы типа Files.walk возвращают поток (Stream<Path>), который можно фильтровать, преобразовывать, собирать в коллекции — всё, что умеет Stream API.

Работа с большими директориями

Старый API мог «упасть», если файлов слишком много (например, 100 000 фото). NIO2 обрабатывает такие случаи легко, так как не загружает всё в память сразу.

Поддержка симлинков, атрибутов, прав доступа

Можно узнать, является ли путь символьной ссылкой (Files.isSymbolicLink(path)), получить права доступа (Files.getPosixFilePermissions(path)), узнать владельца файла и многое другое.

Сравнение старого и нового API

Операция Старый API (File) Новый API (Path, Files)
Проверить существование
file.exists()
Files.exists(path)
Получить размер
file.length()
Files.size(path)
Список файлов в папке
file.listFiles()
Files.list(path)
Рекурсивный обход Рекурсия вручную
Files.walk(path)
Копирование файла file.renameTo() (криво)
Files.copy(src, dest)
Получить расширение Парсить строку
path.getFileName().toString()
Получить родителя
file.getParentFile()
path.getParent()
Работа с правами Почти никак
Files.getPosixFilePermissions(path)

Важные особенности

Проверка типа файла

  • Files.isRegularFile(path) — обычный файл
  • Files.isDirectory(path) — папка
  • Files.isSymbolicLink(path) — симлинк

Работа с большими директориями

  • Не стоит собирать все пути в список: работайте с потоками (Stream<Path>) и обрабатывайте по мере поступления.
  • После окончания работы поток обязательно закрывается (try-with-resources).

Исключения

  • Почти все методы могут бросить IOException — не забывайте обрабатывать ошибки (или пробрасывать выше).

Ограничение глубины обхода

try (var paths = Files.walk(Path.of("mydir"), 2)) { // только 2 уровня
    // ...
}

6. Типичные ошибки при работе с NIO2

Ошибка №1: забыли закрыть поток walk. Если не использовать try-with-resources, можно получить утечку ресурсов — поток файловой системы останется открытым. Всегда используйте конструкцию try (var paths = Files.walk(...)) { ... }.

Ошибка №2: не проверили, что путь — это директория. Если передать в Files.walk путь к файлу, а не к папке, можно получить неожиданное поведение или ошибку.

Ошибка №3: не обработали исключения. Практически все методы NIO2 могут выбросить IOException. Не оставляйте эти ошибки без внимания — хотя бы выведите сообщение пользователю или залогируйте.

Ошибка №4: путаница с разделителями путей. Если вы вручную склеиваете пути через / или \, вы делаете это зря! Используйте Path.of(...) или resolve(...) — они сами разберутся, что к чему.

Ошибка №5: попытка прочитать огромную директорию «в память». Не собирайте все пути в список, если файлов очень много — работайте с потоками (Stream<Path>) и обрабатывайте их по мере поступления.

Ошибка №6: забыли про кроссплатформенность. Не хардкодьте абсолютные пути с Windows- или Unix-стилем. Используйте Path и операции вроде resolve/relativize — они сделают правильно на любой ОС.

1
Задача
JAVA 25 SELF, 39 уровень, 0 лекция
Недоступна
Цифровой инвентаризатор: обзор содержимого папки
Цифровой инвентаризатор: обзор содержимого папки
1
Задача
JAVA 25 SELF, 39 уровень, 0 лекция
Недоступна
Поиск сокровищ разработчика: найдите все Java-файлы
Поиск сокровищ разработчика: найдите все Java-файлы
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Andrey Уровень 1
2 октября 2025
39