ディレクトリ操作

java.nio.file.Filesパッケージを利用して、ディレクトリ操作について説明いたします。
FilesパッケージのAPIを見てください。

一部のディレクトリ操作に関して、ファイルと同じメソッドを利用します。
ファイル操作の場合には、1つのファイルを対象にAPIを利用して処理を行います。
対して、ディレクトリ操作の場合には、ディレクトリ1つのケース以外に、
指定したパスの階層下に存在するファイル、ディレクトリが存在するため、それに対する扱い方でAPIとロジックで表現されます。

FilesAPIへの対応表(ディレクトリ)

ファイルシステムの基本機能に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」配下に以下のデータを用意します。ダウンロードからサンプルデータとサンプルは容易しています。

  • jp/co/educationなどのJavaパッケージとJavaソースファイルを保存します。

ディレクトリの新規作成

ディレクトリの新規作成サンプル
        //呼び出し側
        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」が作成されている必要があります。

ディレクトリの新規作成-createDirectories(Path, java.nio.file.attribute.FileAttribute...)

ディレクトリの新規作成
		//呼び出し側
		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」ディレクトリも作成されます。

ディレクトリの複製-walkFileTree(Path, FileVisitor)を利用

ディレクトリの複製サンプル
        //呼び出し元
        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 );
        }
    }
    				
walkFreeTree概要図

walkFileTreeの引数であるFileVisitorの各種メソッドは、以下の順番で実行されます。

  1. preVisitDirectoryで子階層のディレクトリに最初にアクセスを試みます。アクセスを開始するディレクトリのパスが引数のPathに設定されます。
  2. visitFileで子階層のディレクトリのファイルにアクセスします。アクセスを開始するファイルのパスが引数のPathに設定されます。
  3. visitFileFailedは、子階層のファイルにアクセスした場合に、隠しファイルや欠損ファイルなどアクセスに失敗した場合にメソッドが呼ばれます。
  4. postVisitDirectoryで子階層のディレクトリで子階層の全てのファイルにアクセスし、次の階層のディレクトリの検索を実行します。

ディレクトリの複製-walk(Path,java.nio.file.FileVisitOption...)

ディレクトリの複製サンプル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と同様の動きをします。
そのため、必ず探索するディレクトリに最初にアクセスを試みて、次にアクセスした
ディレクトリのファイルにアクセスします。

ディレクトリのrename

ディレクトリのリネームサンプル
        //----- 前提条件 リネーム前のディレクトリ階層サンプル作成
        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の並び順を階層の深い順に並び替えを行い、
最下位層ディレクトリから削除していき、全てのディレクトリを削除しています。

ディレクトリの検索-walk(Path, java.nio.file.FileVisitOption...)

ファイルの検索と全体の構成や処理の流れは変わりません。指定しているのが、ファイルかディレクトリの違いかです。

ディレクトリ検索のサンプル-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

ディレクトリ検索のサンプル-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 );
        }
    }
    				

サンプルのダウンロードと実行方法

  • java_sample22.zipファイルを「c:/download/java/samples/」に保存して下さい。
  • java_sample22.zipファイルを「c:/projects」配下に展開して下さい。
  • コマンドプロンプトを起動して、「sample22.bat」を実行して下さい。
  • javac,jar,javaが実行されてJavaFilesSampleが実行されます。
  • 「c:/projects/education/logs」配下のログの実行結果を確認して下さい。
  • 「c:/projects/education/input/src」配下のファイルを確認して下さい。
  • 「c:/projects/education/output/copyto」配下のファイルの実行結果を確認して下さい。
  • 「c:/projects/education/output/copyto」に「src」ディレクトリのコピーなどが実行されたことを確認します。
  • 再実行する場合には、「copytoディレクトリ」配下を全て削除してください。、
    copytoディレクトリは事前に必要です。
  • Linux,Unix,iOSの方はバッチファイルはお手数ですが作成して下さい。改行コードが「CRLF」の点ご留意ください。