.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;
}
次のサンプル・プログラムでは、ファイル探索メカニズムが使用されています。