【Java】Filesクラスによるファイル操作
本稿は、Java1.7より追加されたAPIであるFilesクラスで、Fileクラスよりも高度なファイル操作を行う方法について説明します。
Filesクラスは、基本的にPathクラスを引数にするメソッドを多く含みます。
Pathクラスは、FileクラスのtoPathメソッドや、Pathsクラスのgetメソッドでも生成できます。
また、PathクラスからtoFileメソッドでFileに変換することもできます。
今回はそのFilesクラス関連の機能の一部を説明します。
2017/12/25追記
findメソッド、newDirectoryStreamメソッドの戻り値は使用後にcloseを呼び出したほうが良いメソッドみたいなのでtry-with-resource構文を利用したサンプルに修正しました。
2020/05/19追記
椿様から、コンパイルエラーの、ご指摘を受け、walkFileTreeサンプルの修正を行いました。ご連絡ありがとうございます。
newDirectoryStreamメソッド
指定フォルダの中身のファイルとフォルダをPathの反復子で取得します。
まずは、testフォルダを作成します。

その中に、適当なファイルを入れて試します。

import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.nio.file.DirectoryStream;
import java.io.IOException;
public class Test{
	public static void main(String[] args)throws IOException{
		Path path = Paths.get("test");
		try( DirectoryStream<Path> ds = Files.newDirectoryStream(path) ){
			for ( Path p : ds ) {
				System.out.println(p);
			}
		} catch( IOException e ) {
			e.printStackTrace();
		}
	}
}
test\その他
test\僕.txt
test\画像.jpg
test\私.txt
フォルダの中身を表示することができました。
createDirectoryメソッド
Fileオブジェクトの示す場所にフォルダを作成します。
試しにやっほいフォルダを作成してみます。
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.IOException;
public class Test{
	public static void main(String[] args)throws IOException{
		Path path = Paths.get("やっほい");
		Files.createDirectory(path);
	}
}

やっほいフォルダが作成されましたね。
deleteメソッド
Fileオブジェクトの示す場所のファイルまたはフォルダを削除します。
やっほいフォルダを削除してみます。

import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.IOException;
public class Test{
	public static void main(String[] args)throws IOException{
		Path path = Paths.get("やっほい");
		Files.delete(path);
	}
}

やっほいフォルダが削除されましたね。
moveメソッド
ファイルまたはフォルダを移動したり、名前変更したりできます。
テスト.txtファイルをやっほいフォルダに移動してみます。

import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.io.IOException;
public class Test{
	public static void main(String[] args)throws IOException{
		Path src = Paths.get("テスト.txt");
		Path dest = Paths.get("やっほい/テスト.txt");
		Files.move(src,dest);
	}
}
先ほどあった、テスト.txtファイルがなくなりました。

やっほいフォルダの中身

テストファイルが移動されているようです。
copyメソッド
ファイルまたはフォルダをコピーします。
あいえお.txtファイルを作成しました。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Test {
	public static void main(String[] args) throws IOException {
		Path src = Paths.get("あいえお.txt");
		Path dest = Paths.get("あいうえお.txt");
		Files.copy(src, dest);
	}
}

あいえお.txtファイルをコピーした、あいうえお.txtファイルが作成されました。
readAllLinesメソッド
簡易的に文字列を読み込むことができます。
test.txtファイルを実行フォルダに作成し、内容を以下のようにして保存しました。

これを簡易的に読み込む方法が次のプログラムです。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class Test {
	public static void main(String[] args) throws IOException {
		Path path = Paths.get("test.txt");
		List<String> lines = Files.readAllLines(path);
		for ( String line : lines ) {
			System.out.println(line);
		}
	}
}
あいうえお
かきくけこ
保存ファイルがUTF8でない場合は、readAllLinesメソッドの第二引数の文字コードを保存ファイルの文字コード形式としっかり合わせることで読み込ませることができます。
writeメソッド
簡易的にテキストファイルを作成できます。
test.txtに何かテキストを書き出してみましょう。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
public class Test {
	public static void main(String[] args) throws IOException {
		Path path = Paths.get("test.txt");
		ArrayList<String> arr = new ArrayList<>();
		arr.add("こんちゃん");
		arr.add("おんちゃん");
		arr.add("あんちゃん");
		Files.write(path, arr);
	}
}

findメソッド
指定フォルダからの全階層ファイル、フォルダ検索が可能です。
今回は例として、画像という文字列を含むファイルのみを一覧で検索したいと思います。
流石に、キャプチャを全部乗せると見にくいので省略し、階層を以下のように作成しました。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
public class Test{
	public static void main(String[] args)throws IOException{
		Path path = Paths.get("test");
		//検索オブジェクト(画像で、かつファイルの条件)
		BiPredicate<Path, BasicFileAttributes> bp =
				(p,a) -> p.toFile().getName().contains("画像") && a.isRegularFile();
		//引数=(検索フォルダ, 再帰回数, 検索内容)
		try( Stream<Path> stream = Files.find(path, 2 ,bp) ){
			stream
			.map(e->e.toFile().getName())//検索結果をファイル名に変換
			.forEach(System.out::println);//検索結果表示
		} catch ( IOException e ) {
			e.printStackTrace();
		}
	}
}
あの画像.jpg
画像1.jpg
画像2.jpg
音楽画像.jpg
画像という文字を含むファイルのみを検索できていますね。
findメソッドの第二引数の数値は再帰回数です。
回数を多くすると、より深いフォルダまで検索していきます。
今回の例ならば、1に指定するとtestフォルダ内のフォルダとファイルのみが検索され、2に指定するともう一階層下の、画像フォルダと音楽フォルダ内部のファイルとフォルダまで検索してくれます。
そして、検索マッチに関する判断は第三引数に指定します。
(p,a)の左の引数がPathオブジェクトで右の引数がBasicFileAttributesオブジェクトになります。この二つの引数を使って検索に引っ掛けるか判定できます。BasicFileAttributesはファイル属性を表すオブジェクトであり、ファイルサイズ取得やフォルダかどうかなどの判断もできます。
今回の場合はp.toFile().getName().contains(“画像”)で画像を含むファイル名であり、かつ!a.isDirectory()でフォルダではないかを判定して検索に引っ掛けています。
walkFileTreeメソッド
指定フォルダからの全階層ファイル、フォルダの削除やコピーが可能です。
先ほど使用した階層を一斉コピーしたいとします。
コピー対象の階層

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
public class Test {
	public static void main(String[] args) throws IOException {
		Path src = Paths.get("test");
		Path dest = Paths.get("テスト");
		Files.walkFileTree(src, new TestFileVisitor(src, dest));
	}
}
class TestFileVisitor implements FileVisitor<Path>{
	final Path srcPath;
	final Path destPath;
	
	public TestFileVisitor(Path srcPath, Path destPath) {
		this.srcPath = srcPath;
		this.destPath = destPath;
	}
	@Override
	//中のファイルやフォルダなどを処理する前に呼び出される
	public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
		Path targetdir = destPath.resolve(srcPath.relativize(dir));
		Files.copy(dir,targetdir);
		return FileVisitResult.CONTINUE;
	}
	@Override
	//実行すべきファイルの処理を記述する
	public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
		Files.copy(file, destPath.resolve(srcPath.relativize(file)));
		return FileVisitResult.CONTINUE;
	}
	@Override
	//属性読み取り失敗時などに呼び出される
	public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
		return FileVisitResult.CONTINUE;
	}
	@Override
	//フォルダ内のファイルやフォルダなどの処理が全て呼び出された後に呼び出される
	public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
		return FileVisitResult.CONTINUE;
	}
	
}
テストフォルダが作成されました。

中身もちゃんとコピーされています。

ここで出てきたFileVisitorインターフェースの実装により少しプログラムが長くなりましたが、内容はそこまで複雑ではありません。
walkFileTreeメソッドを呼び出すと引数に指定したパスにあるフォルダやファイルを順番にすべて確認していき、確認の度に第二引数のFileVisitorの特定のメソッドを呼び出します。
preVisitDirectoryは、そのフォルダの中身の確認をしに行く前に、呼び出されます。
visitFileはファイルが確認された時に呼び出されます。
visitFileFailedはファイルの属性読み取り失敗時などに呼び出されます。
postVisitDirectoryは、そのフォルダの中身のをすべて確認し終わった後に呼び出されます。
さて、コピーをフォルダごと実装する事を考えると、コピー先のフォルダが先に生成されなければなりませんよね?なので、今回はフォルダの中身を確認しに行く前にコピー元のフォルダを作成します。
これができそうなのはpreVisitDirectoryですよね。
そうするとfileVisitでファイル確認する時にはコピー先フォルダは既にできているはずです。なので普通にコピー処理を実装しておきます。
以上でフォルダコピーの簡易実装が完了しました。
せっかくなのでフォルダごと削除する方法も考えてみましょう。
フォルダを削除しようと思うと、中にファイルやフォルダが存在している状態では削除できません。
つまり、先ほどとは逆に中の物を先に削除し、最後にフォルダを削除する必要があります。
すべて処理した後に実行されるメソッドはpostVisitDirectoryでしたね。
ここにフォルダの削除処理を実装すればいいわけです。
それではコピーで作成したテストフォルダごと削除するサンプルをどうぞ。
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
public class Test {
	public static void main(String[] args) throws IOException {
		Path path = Paths.get("テスト");
		Files.walkFileTree(path, new TestFileVisitor());
	}
}
class TestFileVisitor implements FileVisitor<Path>{
	@Override
	//フォルダ内のファイルやフォルダなどを処理する前に呼び出される
	public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
		return FileVisitResult.CONTINUE;
	}
	@Override
	//実行すべきファイルの処理を記述する
	public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
		Files.delete(file);
		return FileVisitResult.CONTINUE;
	}
	@Override
	//ファイルの処理で属性が読み込めない時などに呼び出される
	public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
		return FileVisitResult.CONTINUE;
	}
	@Override
	//フォルダ内のファイルやフォルダなどの処理が全て呼び出された後に呼び出される
	public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
		Files.delete(dir);
		return FileVisitResult.CONTINUE;
	}
	
}

テストフォルダが削除されました。




ディスカッション
コメント一覧
大変恐縮ですが、walkFileTreeメソッド の 階層を一斉コピー を参考にしたく拝読させていただきました。まるまる書き写したものでさえコンパイルエラーがでてしまいます。
Test.java:17: エラー: TestFileVisitorはabstractでなく、FileVisitor内のabstractメソッドpostVisitDirectory(Object,IOException)をオーバーライドしません
class TestFileVisitor implements FileVisitor{
^
Test.java:25: エラー: メソッドはスーパータイプのメソッドをオーバーライドまたは実装しません
@Override
^
Test.java:32: エラー: メソッドはスーパータイプのメソッドをオーバーライドまたは実装しません
@Override
^
Test.java:38: エラー: メソッドはスーパータイプのメソッドをオーバーライドまたは実装しません
@Override
^
Test.java:43: エラー: メソッドはスーパータイプのメソッドをオーバーライドまたは実装しません
@Override
^
ご迷惑でなければご教授願えませんか? よろしくお願いいたします。
はいよー
今夜確認してみますよー
すみません>< よろしくおねがいしますっ!!!!
これは私が提示しているサンプルに間違いがありますね。
HTML上での記号のエスケープをし忘れていましたm(_ _;)m
修正したのでもう一度コピペしてもらうと動くようになると思います。
ご迷惑をおかけし申し訳ございません。
連絡をしてくださり感謝します。
ご迷惑だなんてとんでもありません! 大変すばらしい記事で勉強させていただきまして、感謝しかありません……。しかもご対応までいただきまして頭があがりません_l ̄l●lll
>>HTML上での記号のエスケープをし忘れていましたm(_ _;)m
やはり誤字のエラーだったのですね……。2時間ほどにらめっこして見つけられなかったのでくやしいです……。後ほど、昨日手元に作ったjavaファイルと見比べて勉強させていただこうと思います。
恥を忍んでお声をかけてよかったです◎ 本当にありがとうございます。今後とも応援しておりますっ!!