java.nio.file.Filesパッケージを利用して、ディレクトリ操作について説明いたします。
FilesパッケージのAPIを見てください。
一部のディレクトリ操作に関して、ファイルと同じメソッドを利用します。
ファイル操作の場合には、1つのファイルを対象にAPIを利用して処理を行います。
対して、ディレクトリ操作の場合には、ディレクトリ1つのケース以外に、
指定したパスの階層下に存在するファイル、ディレクトリが存在するため、それに対する扱い方でAPIとロジックで表現されます。
ファイルシステムの基本機能にjava.nio.file.Filesクラスのディレクトリ関係のメソッドを当て込んでいきましょう。
処理 | 戻り値 | 対応メソッド | 説明 |
---|---|---|---|
新規作成 (create) |
Path | createDirectory (Path dir, FileAttribute<?gt;... attrs) |
新規にディレクトリを作成します。 Pathで指定された親階層全てのディレクトリは作成されていることが前提です。 既にディレクトリが存在す場合には、FileAlreadyExistsExceptionをスローします。 UnsupportedOperationException,IOException,SecurityException 例外をスローする可能性があります FileAttribute<?>は、ディレクトリの属性を決める事ができます。 Java8APIPosixFilePermissions |
Path | createDirectories (Path dir, FileAttribute<?gt;... attrs) |
指定したパスのディレクトリの存在可否に問わず、
親階層から最下位層までディレクトリを構築します。 又は、createDirectoryと異なり、指定したディレクトリが存在した場合には、 FileAlreadyExistsExceptionをスローされません。 UnsupportedOperationException,IOException,SecurityException 例外をスローする可能性があります。 FileAlreadyExistsExceptionは、指定した最下位層のパスにファイルが存在した場合 |
|
複製 (copy) |
- | 無し。 APIを複合的に利用する |
copyメソッドはファイルのみ有効。 単純なcopy( from , to)のようなメソッドはありません。 ディレクトリの子階層のディレクトリとファイルの一覧を処理し、 copyファイルやcreateDirectoriesを利用します。 ファイルの一覧取得にwalkFileTree又はwalkを利用します。 |
名称変更 (rename) |
Path | move
(Path source, Path target, CopyOption... options) |
StandardCopyOption.ATOMIC_MOVEを引数に指定する。 |
移動 (move) |
Path | move (Path source, Path target, CopyOption... options) |
StandardCopyOption.ATOMIC_MOVEを引数に指定する。 |
削除 (delete) |
vold | delete (Path path) |
ディレクトリに配下にディレクトリ・ファイルが存在すると、 java.nio.file.DirectoryNotEmptyExceptionをスローします。 配下の階層を削除するためにdeleteIfExistsとwalkを利用します。 |
検索 (search) |
Stream<Path> | Java8walk ( Path start , FileVisitOption... options ) |
開始位置startを起点として、 startディレクトリ配下のディレクトリと ファイルを網羅したStream<Path>を返します。 ファイルの検索と同じようにwalkTreeView,findを 利用しても実現可能です。 |
Stream<Path> | Java8find (Path start, int maxDepth, BiPredicate<Path,BasicFileAttributes> matcher, FileVisitOption... options) |
指定された開始ディレクトリからmatherに指定した 条件に該当するディレクトリのPathのStreamを返す。 |
java.nio.file.Filesの挙動を確認するためのサンプルを説明します。
サンプルのメンバメソッド
/**
* プロジェクトパス
*/
private Path projectPath = Paths.get( "C:/projects/education" );
/**
* コピー元となるディレクトリ群
*/
private Path inputPath = projectPath.resolve("data/input/copyfrom");
/**
* 出力先となるディレクトリ群
*/
private Path outputPath = projectPath.resolve("data/output/copyto");
java.util.logging.Loggerを利用しています。System.out.prinltnで全て記述しても構いません。
プロジェクトのルートパスを示すprojectPathをメンバ変数として定義します。
「c:/projects/education/data/input/src」配下に以下のデータを用意します。ダウンロードからサンプルデータとサンプルは容易しています。
ディレクトリの新規作成サンプル
//呼び出し側
Path newDir = outputPath.resolve("newdir1");
createOneDirectory( newDir );
/**
* 空のディレクトリを1つ作成する。
* このメソッドはディレクトリが存在した状態は、エラーになります。
* @see java.nio.file.Files#createDirectory(Path, java.nio.file.attribute.FileAttribute...)
* @param 作成するディレクトリ
*/
private void createOneDirectory( Path inPath){
Path resultPath = null;
try {
//ディレクトリが存在するとエラーになります。
resultPath = Files.createDirectory( inPath );
log.info( "ディレクトリ「"+ inPath.toString() + "」の作成が終了しました。" );
checkResult( inPath , resultPath );
} catch (FileAlreadyExistsException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の新規作成処理でエラーが発生しました。", new String[]{ inPath.toString() } );
log.severe("指定されたディレクトリは既に作成されています。" );
log.log( Level.SEVERE, "例外内容:", e );
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の新規作成処理でエラーが発生しました。", new String[]{ inPath.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
createDirectoryは、createDirectoriesメソッドと異なり、指定した最下位層1つ上までのディレクトリは、
事前に作成されている必要があります。
上記例では、「C:/projects/education/data/output」が作成されている必要があります。
ディレクトリの新規作成
//呼び出し側
Path newDir2 = outputPath.resolve("newdir2/newdir21");
createDirectories( newDir2 );
/**
* 指定したディレクトリ階層を全て作成します。
* このメソッドはディレクトリが存在した状態でもエラーになりません。
* @see java.nio.file.Files#createDirectories(Path, java.nio.file.attribute.FileAttribute...)
* @param inDir 作成するディレクトリ
*/
private void createDirectories( Path inDir){
Path resultPath = null;
try {
//ディレクトリが存在するとエラーになります。
resultPath = Files.createDirectories( inDir );
log.info( "ディレクトリ「"+ inDir.toString() + "」の作成が終了しました。" );
checkResult( inDir , resultPath );
} catch (FileAlreadyExistsException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の新規作成処理でエラーが発生しました。", new String[]{ inDir.toString() } );
log.severe("指定されたパス名のファイルが存在しています。" );
log.log( Level.SEVERE, "例外内容:", e );
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の新規作成処理でエラーが発生しました。", new String[]{ inDir.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
createDirectoriesは、createDirectoryと異なり、指定した最下位層のディレクトリから上位階層のディレクトリが、
存在しない場合には、親階層のディレクトリの作成します。
上記例では、「newdir2」ディレクトリも作成されます。
ディレクトリの複製サンプル
//呼び出し元
Path fromDir = inputPath.resolve("src");
Path toDir1 = outputPath.resolve("copy1");
copyDirectories( fromDir , toDir1 );
/**
* 指定したディレクトリを指定したディレクトリ配下にコピーする
* Java8以前でバージョンでstreamやラムダ式が使えない場合の検索方式
* @see java.nio.file.Files#walkFileTree(Path, FileVisitor)
* @param fromDir コピー元ディレクトリ
* @paam toDir コピー先ディレクトリ
*/
private void copyDirectories( Path fromDir , Path toDir ){
int startIndex = fromDir.getNameCount() -1;
log.info("---- copyDirectories START ----" );
log.info( "コピー元ディレクトリ[" + fromDir.toString() + "]から" );
log.info( "コピー先ディレクトリ[" + toDir.toString() + "]へコピーします。" );
try {
Files.walkFileTree(fromDir, new FileVisitor<Path>() {
Path toTarget = null;//コピー先
@Override
/**
* ファイル名を維持してそのまま出力先ディレクトリにコピーする。
*/
public FileVisitResult visitFile(Path file,
BasicFileAttributes attr) {
try {
Files.copy(file, toTarget.resolve( file.getFileName() ));
} catch (IOException e) {
log.log( Level.SEVERE, "ファイル「{0}」のファイル複製でエラーが発生しました。", new String[]{ file.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir,
IOException exc) {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file,
IOException exc) {
return FileVisitResult.CONTINUE;
}
@Override
/**
* これから探索するディレクトリを出力先ディレクトリにも作成する。
*/
public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs)
throws IOException {
toTarget = toDir.resolve( dir.subpath( startIndex , dir.getNameCount() ) );
/*
* 本当は、このように読取元の属性を維持したディレクトリの作成をしたいが、
* Windowsでは、POSIX準拠ではないため属性の指定は、エラーになる。
* Set<PosixFilePermission> permissions = Files.getPosixFilePermissions( dir );
* Files.createDirectories( toTarget , PosixFilePermissions.asFileAttribute( permissions ) ) ;
*/
Files.createDirectories( toTarget ) ;
return FileVisitResult.CONTINUE;
}
} );
log.info("---- copyDirectories END ----" );
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の複製処理でエラーが発生しました。", new String[]{ fromDir.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
walkFileTreeの引数であるFileVisitorの各種メソッドは、以下の順番で実行されます。
ディレクトリの複製サンプルwalkメソッド利用
//呼び出し元
Path fromDir = inputPath.resolve("src");
Path toDir3 = outputPath.resolve("copy3");
copyDirectories( fromDir , toDir3 );
/**
* ディレクトリコピー(walk版)
* Java8以上 try-with-resources,stream
* @see java.nio.file.Files#walk(Path,java.nio.file.FileVisitOption...)
* @param from コピー元のpath
* @param to コピー先のpath
*/
private void copyDirectoriesByWalk( Path fromDir , Path toDir){
int startIndex = fromDir.getNameCount() -1;
try ( Stream<Path> stream = Files.walk( fromDir ) ) {
log.info("---- copyDirectoriesByWalk START ----" );
stream.forEach(new Consumer<Path>() {
@Override
public void accept(Path path) {
Path toTarget = toDir.resolve( path.subpath( startIndex , path.getNameCount() ) );
try {
if( Files.isDirectory( path ) ){
Files.createDirectories( toTarget );
} else {
if( Files.isRegularFile( path ) ){
Files.copy( path, toTarget );
}
}
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の複製処理でエラーが発生しました。", new String[]{ toTarget.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
});
log.info("---- walkSearchFile END ----" );
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の複製処理でエラーが発生しました。", new String[]{ fromDir.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
walkメソッドを利用したディレクトリのコピーのサンプルです。
鋭い方は、これでディレクトリとファイル漏れないかと思われると思います。
walkメソッドのディレクトリ探索の動きはwalkFileTreeと同様の動きをします。
そのため、必ず探索するディレクトリに最初にアクセスを試みて、次にアクセスした
ディレクトリのファイルにアクセスします。
ディレクトリのリネームサンプル
//----- 前提条件 リネーム前のディレクトリ階層サンプル作成
Path toDir6 = outputPath.resolve("copy6");
copyDirectories( fromDir , toDir6 );
Path toDir7 = outputPath.resolve("copy7");
renameDirecotry( toDir6, toDir7 );
/**
* ディレクトリの名称変更
* @param fromDir 変更前の名称
* @param toDir 変更後の名称
*/
private void renameDirecotry( Path fromDir , Path toDir) {
moveDirectory( fromDir , toDir );
}
ディレクトリの移動と同じメソッドを利用します。詳しくは次のサンプルをご覧ください。
ディレクトリの移動サンプル
//----- 前提条件 移動前のcopy4ディレクトリサンプル作成
Path toDir4 = outputPath.resolve("copy4");
copyDirectories( fromDir , toDir4 );
Path toDir5 = outputPath.resolve("copy5");
moveDirectory( toDir4 , toDir5);
/**
* ディレクトリを移動する又はrenameをする。
* @param fromDir 移動前ディレクトリ
* @param toDir 移動先ディレクトリ
*/
private void moveDirectory( Path fromDir , Path toDir ){
log.info("---- moveDirectory START ----" );
try {
Files.move(fromDir, toDir, java.nio.file.StandardCopyOption.ATOMIC_MOVE );
} catch (IOException e) {
log.severe( "ディレクトリ[" + fromDir.toString() + "]から" );
log.severe( "ディレクトリ[" + toDir.toString() + "]の移動に失敗しました" );
log.log( Level.SEVERE, "例外内容:", e );
}
log.info( "ディレクトリ[" + fromDir.toString() + "]から" );
log.info( "ディレクトリ[" + toDir.toString() + "]の移動に成功しました。" );
log.info("---- moveDirectory END ----" );
}
指定したディレクトリを指定したディレクトリに移動させます。
java.nio.file.StandardCopyOption.ATOMIC_MOVEでファイルの存在可否に関係なく移動(状況によっては、上書き)します。
空ディレクトリの削除であればFiles.delete( Path path)
で済みます。
ディレクトリの削除は対外、子階層のディレクトリやファイルも伴います。
そのため、以下のようなサンプルになります。
ディレクトリの削除(子階層のファイル及びディレクトリも削除)
//----- 前提条件 削除予定の階層を作る
Path toDir8 = outputPath.resolve("copy8");
copyDirectories( fromDir , toDir8 );
deleteDirectoies( toDir8 );
/**
* ディレクトリを削除する。
* @parma delPath 削除対象ディレクトリ
*/
private void deleteDirectoies( Path delPath ){
List<Path> dirList = new ArrayList<Path>();
try ( Stream<Path> stream = Files.walk( delPath ) ) {
log.info("---- deleteDirectoies START ----" );
//ファイルは全て削除する。
stream.forEach(new Consumer<Path>() {
@Override
public void accept(Path path) {
try {
if( Files.isDirectory( path ) ){
dirList.add( path );
} else {
if( Files.isRegularFile( path ) ){
Files.deleteIfExists( path );
}
}
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の削除処理中にエラーが発生しました。", new String[]{ path.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
});
//階層の深い順番にディレクトリを列挙して、削除する。
Comparator<Path> pathComparetor = (e1, e2) -> Integer.compare( e2.getNameCount(), e1.getNameCount());
Stream<Path> streamDir = dirList.stream();
streamDir.sorted( pathComparetor ).forEach( e -> {
try {
Files.deleteIfExists(e);
} catch (IOException e3) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の削除処理でエラーが発生しました。", new String[]{ e.toString() } );
log.log( Level.SEVERE, "例外内容:", e3 );
}
} );
log.info("---- deleteDirectoies END ----" );
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」の削除処理でエラーが発生しました。", new String[]{ delPath.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
最初にwalkを利用して、ディレクトリ階層の一覧を取得します。
stream.forEachで先に階層上のファイルは全て削除します。
forEach分でリストにディレクトリのPathを保存します。
最後にディレクトリのPathの並び順を階層の深い順に並び替えを行い、
最下位層ディレクトリから削除していき、全てのディレクトリを削除しています。
ファイルの検索と全体の構成や処理の流れは変わりません。指定しているのが、ファイルかディレクトリの違いかです。
ディレクトリ検索のサンプル-walkFileTree(Path, FileVisitor)
//copy1ディレクトリからcoディレクトリを探す
Path toDir1 = outputPath.resolve("copy1");
walkSearchDir( toDir1 , "co" );
/**
* ディレクトリ階層の検索を行い、指定したファイルのパスを取得する。
* @see java.nio.file.Files#walk(Path, java.nio.file.FileVisitOption...)
* @param path ディレクトリの探索場所
* @param targetDir ディレクトリ名
*/
private void walkSearchDir( Path path , String targetDir){
log.info("---- START walkSearchDir ----" );
try ( Stream<Path> stream = Files.walk( path ) ) {
//Java8から利用可能 ラムダ式利用
Stream<Path> streamPath = stream
.filter( entry -> entry.getFileName().toString().equals( targetDir ) );
streamPath.forEach( System.out::println );
log.info("---- END walkSearchDir ----" );
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」のファイル検索(walk)処理でエラーが発生しました。", new String[]{ targetDir } );
log.log( Level.SEVERE, "例外内容:", e );
}
}
ディレクトリ検索のサンプル-find
//呼び出し側
//nioディレクトリを探す
findSearchDir( toDir1 , "nio" );
/**
* ディレクトリ階層の検索を行い、指定したディレクトリのパスを取得する。
* @see java.nio.file.Files#find(Path, int, java.util.function.BiPredicate, java.nio.file.FileVisitOption...)
* @param path 探索するディレクトリ階層
* @param targetDir 探索するディレクトリ名
*/
private void findSearchDir( Path path , String targetDir ){
log.info("---- START findSearchDir ----" );
//Integer.MAX_VALUEは階層数が無制限で探索することを意味します。
//Java8から利用可能 ラムダ式利用
try ( Stream<Path> stream = Files.find( path , Integer.MAX_VALUE ,
(file, attrs) -> file.getFileName().toString().equals( targetDir ) ) ) {
stream.forEach( System.out::println );
log.info("---- END findSearchDir ----" );
} catch (IOException e) {
log.log( Level.SEVERE, "ディレクトリ「{0}」のディレクトリ検索(find)処理でエラーが発生しました。", new String[]{ path.toString() } );
log.log( Level.SEVERE, "例外内容:", e );
}
}