«1. Обзор

В этой статье мы рассмотрим интересную особенность NIO2 — интерфейс FileVisitor.

Все операционные системы и несколько сторонних приложений имеют функцию поиска файлов, где пользователь определяет критерии поиска.

Этот интерфейс нужен нам для реализации такой функциональности в Java-приложении. Если вам нужно найти все файлы .mp3, найти и удалить файлы .class или найти все файлы, к которым не обращались за последний месяц, то этот интерфейс — то, что вам нужно.

Все классы, необходимые для реализации этой функциональности, объединены в один пакет:

import java.nio.file.*;

2. Как работает FileVisitor

С интерфейсом FileVisitor вы можете перемещаться по дереву файлов на любую глубину и выполнять любые действия. на файлы или каталоги, найденные в любой ветке.

Типичная реализация интерфейса FileVisitor выглядит следующим образом:

public class FileVisitorImpl implements FileVisitor<Path> {

    @Override
    public FileVisitResult preVisitDirectory(
      Path dir, BasicFileAttributes attrs) {
        return null;
    }

    @Override
    public FileVisitResult visitFile(
      Path file, BasicFileAttributes attrs) {
        return null;
    }

    @Override
    public FileVisitResult visitFileFailed(
      Path file, IOException exc) {       
        return null;
    }

    @Override
    public FileVisitResult postVisitDirectory(
      Path dir, IOException exc) {    
        return null;
    }
}

Четыре метода интерфейса позволяют указать требуемое поведение в ключевых точках процесса обхода: перед доступом к каталогу, при посещении файла или при возникновении сбоя и после доступа к каталогу соответственно.

Возвращаемое значение на каждом этапе имеет тип FileVisitResult и управляет потоком обхода. Возможно, вы хотите пройтись по дереву файлов в поисках определенного каталога и завершить процесс, когда он будет найден, или вы хотите пропустить определенные каталоги или файлы.

FileVisitResult — это перечисление из четырех возможных возвращаемых значений для методов интерфейса FileVisitor:

    FileVisitResult.CONTINUE — указывает, что обход дерева файлов должен продолжаться после того, как возвращающий его метод завершит работу. FileVisitResult.TERMINATE — останавливает файл. обход дерева и никакие другие каталоги или файлы не посещаются FileVisitResult.SKIP_SUBTREE — этот результат имеет смысл только при возврате из API preVisitDirectory, в других местах он работает как CONTINUE. Это указывает на то, что текущий каталог и все его подкаталоги следует пропустить. Если вызывается на этапе preVisitDirectory, то даже текущий каталог пропускается, а postVisitDirectory не вызывается

Наконец, должен быть способ инициировать процесс обхода, возможно, когда пользователь нажимает кнопку поиска в графическом пользовательском интерфейсе. после определения критериев поиска. Это самая простая часть.

Нам просто нужно вызвать статический API walkFileTree класса Files и передать ему экземпляр класса Path, который представляет начальную точку обхода, а затем экземпляр нашего FileVisitor:

Path startingDir = Paths.get("pathToDir");
FileVisitorImpl visitor = new FileVisitorImpl();
Files.walkFileTree(startingDir, visitor);

3. Файл Пример поиска

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

Когда мы находим файл, мы выводим на экран сообщение об успехе, а когда выполняется поиск по всему дереву файлов, но файл не найден, мы также выводим соответствующее сообщение об ошибке.

3.1. Основной класс

Мы назовем этот класс FileSearchExample.java:

public class FileSearchExample implements FileVisitor<Path> {
    private String fileName;
    private Path startDir;

    // standard constructors
}

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

В следующих подразделах мы реализуем каждый метод интерфейса и обсудим его роль в данном конкретном примере приложения.

3.2. API preVisitDirectory

Начнем с реализации API preVisitDirectory:

@Override
public FileVisitResult preVisitDirectory(
  Path dir, BasicFileAttributes attrs) {
    return CONTINUE;
}

Как мы уже говорили ранее, этот API вызывается каждый раз, когда процесс встречает новый каталог в дереве. Его возвращаемое значение определяет, что произойдет дальше, в зависимости от того, что мы решим. Это точка, в которой мы пропустим определенные каталоги и исключим их из пространства выборки поиска.

«Давайте решим не различать какие-либо каталоги и просто искать во всех них.

3.3. API visitFile

Далее мы реализуем API visitFile. Здесь происходит основное действие. Этот API вызывается каждый раз при обнаружении файла. Мы воспользуемся этим, чтобы проверить атрибуты файла и сравнить с нашими критериями и вернуть соответствующий результат:

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
    String fileName = file.getFileName().toString();
    if (FILE_NAME.equals(fileName)) {
        System.out.println("File found: " + file.toString());
        return TERMINATE;
    }
    return CONTINUE;
}

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

Однако здесь так много всего можно сделать, особенно после прочтения раздела «Атрибуты файла». Вы можете проверить время создания, время последнего изменения или время последнего доступа или несколько атрибутов, доступных в параметре attrs, и принять соответствующее решение.

3.4. API-интерфейс visitFileFailed

Далее мы реализуем API-интерфейс visitFileFailed. Этот API вызывается, когда определенный файл недоступен для JVM. Возможно, он был заблокирован другим приложением или это может быть просто проблема с правами доступа:

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
    System.out.println("Failed to access file: " + file.toString());
    return CONTINUE;
}

Мы просто регистрируем сообщение об ошибке и продолжаем обход остального дерева каталогов. В графическом приложении вы можете спросить пользователя, следует ли ему продолжать или нет, используя диалоговое окно, или просто запишите сообщение где-нибудь и скомпилируйте отчет для последующего использования.

3.5. API postVisitDirectory

Наконец, мы реализуем API postVisitDirectory. Этот API вызывается каждый раз, когда каталог полностью пройден:

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc){
    boolean finishedSearch = Files.isSameFile(dir, START_DIR);
    if (finishedSearch) {
        System.out.println("File:" + FILE_NAME + " not found");
        return TERMINATE;
    }
    return CONTINUE;
}

Мы используем API Files.isSameFile, чтобы проверить, является ли только что пройденный каталог тем каталогом, с которого мы начали обход. Если возвращаемое значение истинно, это означает, что поиск завершен и файл не найден. Таким образом, мы завершаем процесс с сообщением об ошибке.

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

Теперь мы можем добавить наш основной метод для выполнения приложения FileSearchExample:

public static void main(String[] args) {
    Path startingDir = Paths.get("C:/Users/user/Desktop");
    String fileToSearch = "hibernate-guide.txt"
    FileSearchExample crawler = new FileSearchExample(
      fileToSearch, startingDir);
    Files.walkFileTree(startingDir, crawler);
}

Вы можете поэкспериментировать с этим примером, изменив значения переменных startDir и fileToSearch. Когда fileToSearch существует в начальном каталоге или любом из его подкаталогов, вы получите сообщение об успешном выполнении, иначе сообщение об ошибке.

4. Заключение

В этой статье мы рассмотрели некоторые из менее часто используемых функций, доступных в API файловой системы Java 7 NIO.2, в частности интерфейс FileVisitor. Нам также удалось пройти этапы создания приложения для поиска файлов, чтобы продемонстрировать его функциональность.

Полный исходный код примеров, используемых в этой статье, доступен в проекте Github.