このページを正しく表示するには、ブラウザでJavaScriptを有効にする必要があります。
コース: 重要なクラス
レッスン: 基本的なI/O
セクション: ファイルI/O(NIO.2を含む)
ファイル・ツリーの探索
ホームページ > 重要なクラス > 基本的なI/O

ファイル・ツリーの探索

ファイル・ツリー内のすべてのファイルに再帰的にアクセスするアプリケーションを作成する必要はありませんか。 たとえば、ツリー内のすべての.classファイルを削除する、昨年アクセスされなかったすべてのファイルを検索するといったケースです。 このようなアプリケーションは、FileVisitorインタフェースを使用して作成できます。

このセクションでは次の内容について説明します。

FileVisitorインタフェース

ファイル・ツリーを探索するには、まずFileVisitorインタフェースを実装する必要があります。 FileVisitorは、探索プロセス内の重要なタイミングにおける必要な処理を指定するものです。 重要なタイミングとは、ファイルがアクセスされたとき、ディレクトリがアクセスされる前、ディレクトリがアクセスされた後、エラーが発生したときを指します。 このインタフェースには、これらの状況に対応する次の4つのメソッドが含まれています。

FileVisitorのこれら4つのメソッドすべてを実装する必要がない場合は、FileVisitorインタフェースを実装するのではなく、SimpleFileVisitorクラスを継承できます。 このクラスはFileVisitorインタフェースを実装しており、ツリー内のすべてのファイルにアクセスして、エラーが発生した場合はIOErrorをスローします。 このクラスを継承し、必要なメソッドのみをオーバーライドできます。

次のサンプル・プログラムでは、SimpleFileVisitorを拡張して、ファイル・ツリー内のすべてのエントリを出力しています。 また、エントリが通常ファイル、シンボリック・リンク、ディレクトリ、またはその他の"不明な"ファイルのどれであるかを出力します。 さらに、各ファイルのサイズをバイト単位で出力します。 発生した例外があれば、コンソールに出力します。

FileVisitorメソッドについては、太字で示しています。

import static java.nio.file.FileVisitResult.*;

public static class PrintFiles extends SimpleFileVisitor<Path> {

    //各ファイルの種類に関する情報を出力します。
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
        if (attr.isSymbolicLink()) {
            System.out.format("Symbolic link: %s ", file);
        } else if (attr.isRegularFile()) {
            System.out.format("Regular file: %s ", file);
        } else {
            System.out.format("Other: %s ", file);
        }
        System.out.println("(" + attr.size() + "bytes)");
        return CONTINUE;
    }

    //アクセスした各ディレクトリについて出力します。
    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        System.out.format("Directory: %s%n", dir);
        return CONTINUE;
    }

    //ファイルへのアクセス中にエラーが発生した場合は、ユーザーに通知します。
    //このメソッドをオーバーライドしていない場合、エラーが発生すると、 
    //IOExceptionがスローされます。
    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        System.err.println(exc);
        return CONTINUE;
    }
}

プロセスの開始

FileVisitorを実装した場合、ファイルの探索をどのように開始すればよいでしょうか。 Filesクラスには次の2つのwalkFileTreeメソッドがあります。

1つ目のメソッドに必要なのは、開始ポイントを示すパスとFileVisitorのインスタンスだけです。 次のようにして、PrintFilesファイル・ビジターを呼び出すことができます。

Path startingDir = ...;
PrintFiles pf = new PrintFiles();
Files.walkFileTree(startingDir, pf);

2つ目のwalkFileTreeメソッドでは、1つ目のメソッドの引数に加えて、アクセスする最大の深さとFileVisitOption列挙型(Enum)のセットを指定できます。 このメソッドでファイル・ツリー全体が探索されるようにするには、最大の深さを示す引数にInteger.MAX_VALUEを指定できます。

FileVisitOption列挙型のFOLLOW_LINKS列挙定数を指定できます。これは、シンボリック・リンクをたどる必要があることを示します。

次のコードは、4つの引数のメソッドを呼び出す方法を示したものです。

import static java.nio.file.FileVisitResult.*;

Path startingDir = ...;

EnumSet<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);

Finder finder = new Finder(pattern);
Files.walkFileTree(startingDir, opts, Integer.MAX_VALUE, finder);

FileVisitor作成時の考慮事項

ファイル・ツリーは深さ優先(Depth First)で探索されますが、複数のサブディレクトリにアクセスする反復処理の順序は推測できません。

プログラムでファイル・システムを切り替える場合は、FileVisitorの実装方法を慎重に検討する必要があります。

たとえば、再帰的な削除処理を記述する場合は、ディレクトリ内のファイルを削除してからディレクトリ自体を削除します。 この場合、ディレクトリの削除はpostVisitDirectoryメソッドで行います。

再帰的なコピー処理を記述する場合、preVisitDirectoryメソッドで新しいディレクトリを作成してから、visitFilesメソッドでそのディレクトリへのファイルのコピーを試みます。 コピー元ディレクトリの属性を維持する場合(UNIX cp -pコマンドと同様)、ファイルをコピーしたに、postVisitDirectoryメソッドでそれを行う必要があります。 サンプルのCopyで、この方法について示しています。

ファイル検索処理を記述する場合、visitFileメソッドで比較を実行します。 このメソッドは、条件に一致するすべてのファイルを検索しますが、ディレクトリは検索しません。 ファイルとディレクトリの両方を検索する場合は、preVisitDirectoryメソッドとpostVisitDirectoryメソッドのいずれかでも比較を実行する必要があります。 サンプルのFindで、この方法について示しています。

シンボリック・リンクをたどるかどうかを指定する必要があります。 たとえば、ファイルを削除する場合は、シンボリック・リンクをたどることは通常はお勧めしません。 ファイル・ツリーをコピーする場合には、シンボリック・リンクをたどることもあります。 デフォルトでは、walkFileTreeがシンボリック・リンクをたどることはありません。

visitFileメソッドはファイルに対して呼び出されます。 FOLLOW_LINKSオプションを指定しており、かつ、ファイル・ツリーに親ディレクトリへの循環リンクが含まれている場合は、ループするディレクトリがvisitFileFailedメソッドでFileSystemLoopExceptionとして報告されます。 次のコードは、サンプルのCopyの一部であり、循環リンクをキャッチする方法を示したものです。

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
    if (exc instanceof FileSystemLoopException) {
        System.err.println("cycle detected: " + file);
    } else {
        System.err.format("Unable to copy: %s: %s%n", file, exc);
    }
    return CONTINUE;
}

このようなケースは、プログラムでシンボリック・リンクをたどる場合にのみ発生します。

フロー制御

ファイル・ツリーを探索して特定のディレクトリを検索し、それが見つかった時点でプロセスを終了すべき場合があるかもしれません。 また、特定のディレクトリをスキップする必要がある場合もあるでしょう。

FileVisitorの各メソッドは、FileVisitResult値を返します。 各FileVisitorメソッドで次の値を返すことで、ファイル探索プロセスを終了したり、ディレクトリにアクセスするかどうかを制御したりできます。

次のコードでは、SCCSという名前のディレクトリがスキップされます。

import static java.nio.file.FileVisitResult.*;

public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
    (if (dir.getFileName().toString().equals("SCCS")) {
         return SKIP_SUBTREE;
    }
    return CONTINUE;
}

次のコードでは、あるファイルが見つかった時点で、ファイル名を標準出力に出力し、ファイル探索を終了します。

import static java.nio.file.FileVisitResult.*;

//検索するファイル
Path lookingFor = ...;

public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
    if (file.getFileName().equals(lookingFor)) {
        System.out.println("Located file: " + file);
        return TERMINATE;
    }
    return CONTINUE;
}

サンプル・プログラム

次のサンプル・プログラムでは、ファイル探索メカニズムが使用されています。


サンプル・プログラムで問題が発生した場合は、 Compiling and Running the Examples: FAQsを参照してください。
フィードバックをお寄せください。さまざまなご意見をお待ちしております。

前のページ: シンボリック・リンクとその他のリンク
次のページ: ファイルの検索