.class
ファイルを削除する、昨年アクセスされなかったすべてのファイルを検索するといったケースです。 このようなアプリケーションは、FileVisitor
インタフェースを使用して作成できます。
このセクションでは次の内容について説明します。
ファイル・ツリーを探索するには、まずFileVisitor
インタフェースを実装する必要があります。 FileVisitor
は、探索プロセス内の重要なタイミングにおける必要な処理を指定するものです。 重要なタイミングとは、ファイルがアクセスされたとき、ディレクトリがアクセスされる前、ディレクトリがアクセスされた後、エラーが発生したときを指します。 このインタフェースには、これらの状況に対応する次の4つのメソッドが含まれています。
preVisitDirectory
– ディレクトリのエントリがアクセスされる前に呼び出されます。
postVisitDirectory
– ディレクトリのすべてのエントリがアクセスされた後に呼び出されます。 エラーが発生している場合は、特定の例外がこのメソッドに渡されます。
visitFile
– ファイルにアクセスするときに呼び出されます。 ファイルのBasicFileAttributes
がメソッドに渡されます。または、ファイル属性パッケージを使用して、固有の属性セットを読み取ることができます。 たとえば、ファイルのDosFileAttributeView
を読み取って、そのファイルに"隠し"ビット・セットが含まれるかどうかを判断できます。
visitFileFailed
– ファイルにアクセスできないときに呼び出されます。 特定の例外がこのメソッドに渡されます。 例外をスローする、例外をコンソールやログ・ファイルに出力するなどの操作を実行できます。
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
メソッドがあります。
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);
ファイル・ツリーは深さ優先(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
メソッドで次の値を返すことで、ファイル探索プロセスを終了したり、ディレクトリにアクセスするかどうかを制御したりできます。
CONTINUE
– ファイルの探索を続行することを示します。 preVisitDirectory
メソッドがCONTINUE
を返す場合は、ディレクトリがアクセスされます。
TERMINATE
– ファイルの探索を即座に終了します。 この値が返された後は、ファイル探索メソッドが呼び出されることはありません。
SKIP_SUBTREE
– preVisitDirectory
がこの値を返す場合は、対象のディレクトリとそのサブディレクトリがスキップされます。 つまり、このディレクトリのエントリはアクセスされません。
SKIP_SIBLINGS
– preVisitDirectory
がこの値を返す場合は、対象のディレクトリはアクセスされず、postVisitDirectory
は呼び出されず、この時点でアクセスされていない兄弟エントリはアクセスされなくなります。 postVisitDirectory
メソッドがこの値を返す場合は、これ以降、兄弟エントリはアクセスされません。 原則として、対象のディレクトリではその後は何も行われません。
次のコードでは、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; }
次のサンプル・プログラムでは、ファイル探索メカニズムが使用されています。