Javaについて徹底解説!

具体例多数! FileとNIO.2を使ったJavaでのファイル操作

大石 英人

開発エンジニア/Java20年/Java GOLD/リーダー/ボールド歴2年

java.io.Fileは、Javaでファイルやディレクトリ(=フォルダ)の操作を行えるクラスです。ファイルの作成、削除、存在確認など、プログラム上で必要な操作を一通り行えますので、ぜひ使い方を覚えたいものですね。

なお、Java 7で導入されたMore New I/O APIs(NIO.2)により、Fileは公式には過去のものとなりました。でも、Fileは今後もあり続けますし、Fileを使うプログラムやフレームワークは多いので、プログラミングの現場ではまだまだ現役です。

この記事では、FileNIO.2を使った各種ファイル・ディレクトリ操作をざっと紹介します。参考になるものも多いと思いますので、ぜひ読んでいってください。

※この記事のサンプルは、Java 10の環境で動作確認しています。


1.Fileはファイル・ディレクトリを表現したもの

java.io.Fileは、何か一つのファイルあるいはディレクトリ(=フォルダ)を表現しているクラスです。Javaでのファイル操作はこのFileを通して行いますので、全てはFileのインスタンスを生成したり取得するところから始まります。

Fileのインスタンスの生成の仕方は簡単で、以下のいずれかのコンストラクタを使います。何かのパスを表現するStringを引数に取るものが良く使われるものでしょう。他には、親ディレクトリを別のFileで指定するものも良く使います。

// java.io.Fileのコンストラクタ
public File(String pathname)
	pathname - パス名文字列
public File(String parent, String child)
	parent - 親パス名文字列
	child - 子パス名文字列
public File(File parent, String child)
	parent - 親抽象パス名
	child - 子パス名文字列
public File(URI uri)
	uri - 階層型の絶対URI。

Fileのインスタンスを生成する例は以下のものです。ちなみに、Fileのインスタンスを生成する時は、実際のPC・サーバ上にそのファイル・ディレクトリがなくてもエラーにはなりません。必要なだけどんどんnewしましょう。

File file1 = new File("/A/B/C.txt"); // UNIX系OSの場合
File file2 = new File("C:\\A\\B\\C.txt"); // Windowsの場合(実際には"/"でも問題はありません)
File dir = new File("/A/B");
File file3 = new File(dir, "D.csv"); // → /A/B/D.csvを意味するFile

2.FileとNIO.2によるファイル・ディレクトリ操作の例

ここでは、Fileを使ったファイル・ディレクトリ操作の例を示します。それぞれ、実際のプログラミングでよく使うものです。参考として、Java 7から導入されたMore New I/O APIs(NIO.2)ではどのようになるかも併記します。

なお、それぞれ処理に失敗した場合はIOExceptionthrowする場合がありますので、適切にtry-catchをしてください。

2-1.ファイル・ディレクトリを作成する

2-1-1.新規にファイルを作成する(createNewFile)

File.createNewFileを使うと、Fileが表現するファイルを新規に作成できます。作成できたかどうかは戻り値のbooleanで判断します。

File file = new File("/A/B/C.txt");
if (file.createNewFile()) {
	System.out.println(file.getAbsolutePath() + "を作成しました。");
}
// NIO.2で同じことをする場合
Path file = Paths.get("/A/B/C.txt");
Files.createFile(file);

2-1-2.新規にディレクトリを作成する(mkdir/mkdirs)

File.mkdirあるいはmkdirsを使うと、ディレクトリを作成できます。mkdirはパスの一番最後のディレクトリだけを作り、mkdirsはパスのディレクトリの中で存在しないものは全て作ります。

ディレクトリを作成できたかどうかは、戻り値のbooleanで判断します。

File dir = new File("/A/B/C");
dir.mkdir(); // Cだけ作成を試みる
// dir.mkdirs(); // → 中間のディレクトリ(/A/B)含めて、全ディレクトリの作成を試みる
// NIO.2で同じことをする場合
Path dir = Paths.get("/A/B/C");
Files.createDirectory(dir);
// Files.createDirectories(dir); // File.mkdirsと同じ動き

2-1-3.一時ファイルを作成する

File.createTempFileを使うと、一時ファイルを作成できます。処理内で一時的に使いたいファイルを作成するにはこれが使えます。ファイル名の前後に何を付けるか、ファイルの作成先でメソッドが違います。

ディレクトリを指定しない場合は、システムプロパティjava.io.tmpdirのディレクトリに作成されます。具体的にどこになるかは、OSによります。

File file1 = File.createTempFile("AAA", "ZZZ"); // → ファイルAAA14772876267833857426ZZZが作成された
File dir = new File("/tmp");
File file2 = File.createTempFile("AAA", "ZZZ", dir); // 一時ディレクトリを呼び出し側で指定する場合
// NIO.2で同じことをする場合
Path file1 = Files.createTempFile("AAA", "ZZZ");
Path dir = Paths.get("/tmp");
Path file2 = Files.createTempFile(dir, "AAA", "ZZZ");

2-2.ファイル・ディレクトリを削除する

2-2-1.ファイルを削除する(delete)

ファイルを削除するにはFile.deleteを使います。削除できたかどうかは、戻り値のbooleanで判断します。

File file = new File("/A/B/C.txt");
if (file.delete()) {
	System.out.println(file.getAbsolutePath() + "を削除しました。");
}
// NIO.2で同じことをする場合
Path file = Paths.get("/A/B/C.txt");
Files.delete(file);

2-2-2.ディレクトリを削除する(delete)

ディレクトリを削除する場合もFile.deleteを使います。ただし、ディレクトリの内容が空でなければ削除はできません。事前にファイルやサブディレクトリを削除しておきましょう。

File dir = new File("/A/B/C");
if (dir.delete()) {
	System.out.println(dir.getAbsolutePath() + "を削除しました。");
}
// NIO.2で同じことをする場合
Path dir = Paths.get("/A/B/C");
Files.delete(dir);

2-3.ファイル・ディレクトリをリネーム、コピーする

2-3-1.ファイル・ディレクトリをリネームする(renameTo)

ファイルやディレクトリをリネームするには、変更前と変更後の二つのFileを用意し、変更前のFileFile.renameToを実行します。リネームに成功したかは、戻り値のbooleanで確認します。

File from = new File("/A/B/C.txt");
File to = new File("/A/B/D.txt");
if (from.renameTo(to)) {
	System.out.println(from.getAbsolutePath() + "を" + to.getAbsolutePath() + "へリネームしました。");
}
// NIO.2で同じことをする場合
Path from = Paths.get("/A/B/C.txt");
Path to = Paths.get("/A/B/D.txt");
Files.move(from, to);

2-3-2.ファイルをコピーする

Fileには、ファイルをコピーするためのメソッドがありません。そのため、ファイルコピー処理を自分で作るか、色々なフレームワークなどにあるものを使います。

以下のサンプルでは一度に全データをメモリ上に読み込んでいますので、非常に大きなファイルのコピーには向いていません。その場合はループをして少しずつ読み込み・書き込みをすべきです。

File from = new File("/A/B/C.txt");
File to = new File("/A/B/D.txt");
byte[] bytes = new byte[(int) from.length()];

try (FileInputStream fis = new FileInputStream(from);
		FileOutputStream fos = new FileOutputStream(to)) {
	fis.read(bytes);
	fos.write(bytes);
}

あるいは、NIOChannelと呼ばれるクラスを使ってコピーをする方法もあります。こちらの方が高速です。

File from = new File("/A/B/C.txt");
File to = new File("/A/B/D.txt");

try (FileInputStream fis = new FileInputStream(from);
		FileOutputStream fos = new FileOutputStream(to);
		FileChannel fromCh = fis.getChannel();
		FileChannel toCh = fos.getChannel()) {
	toCh.transferFrom(fromCh, 0, fromCh.size());
}
// NIO.2で同じことをする場合
Path from = Paths.get("/A/B/C.txt");
Path to = Paths.get("/A/B/D.txt");
Files.copy(from, to);

2-4.一覧の取得、存在の確認、ファイルサイズを取得する

2-4-1.ディレクトリ内のファイル一覧を得る(listFiles)

File.listFilesでは、ディレクトリ内のファイル・ディレクトリの内容をFileの配列で取得できます。Stringで良いなら、File.listでも良いでしょう。

File dir = new File("/A/B");
File[] entries = dir.listFiles();
for (File e : entries) {
	System.out.println(e.getAbsolutePath());
}
// NIO.2で同じことをする場合
Path dir = Paths.get("/A/B");
List<Path> entries = Files.list(dir).collect(Collectors.toList()); // listではStream<Path>が戻る
for (Path e : entries) {
	System.out.println(e.toAbsolutePath().toString());

2-4-2.ファイル・ディレクトリの存在を確認する(exists)

ファイルやディレクトリの存在確認をしたいなら、File.existsが使えます。存在する場合はtrueが戻ります。

File file = new File("/A/B/C.txt");
if (file.exists()) {
	System.out.println(file.getAbsolutePath() + "は存在します。");
}
// NIO.2で同じことをする場合
Path file = Paths.get("/A/B/C.txt");
if (Files.exists(file)) {
	System.out.println(file.toAbsolutePath().toString() + "は存在します。");
}

2-4-3.Fileの実体が何か確認する(isFile/isDirectory)

Fileがファイルかディレクトリか判断するには、File.isFileあるいはisDirectoryを使います。

File file = new File("/A/B/C.txt");
if (file.isFile()) {
	System.out.println(file.getAbsolutePath() + "はファイルです。");
} else if (file.isDirectory()) {
	System.out.println(file.getAbsolutePath() + "はディレクトリです。");
}
// NIO.2で同じことをする場合
Path file = Paths.get("/A/B/C.txt");
if (Files.isRegularFile(file)) {
	System.out.println(file.toAbsolutePath().toString() + "はファイルです。");
} else if (Files.isDirectory(file)) {
	System.out.println(file.toAbsolutePath().toString() + "はディレクトリです。");
}

2-4-4.ファイルサイズを取得する(length)

File.lengthは、ファイルのサイズをlongで戻します。

File file = new File("/A/B/C.txt");
long size = file.length();
System.out.println("ファイルサイズは" + size + "バイトです。");
// NIO.2で同じことをする場合
Path file = Paths.get("/A/B/C.txt");
long size = Files.size(file);
System.out.println("ファイルサイズは" + size + "バイトです。");

2-5.ファイル入出力をする

ここからは、直接的なFileの使い方ではありませんが、ファイルの読み書きについて記述します。バイナリファイル・テキストファイルでやり方が違いますので、それぞれ記述します。

2-5-1.ファイルの内容をすべて読み込む(バイナリファイル)

バイナリファイルを読み込む時は、FileInputStreamを使います。この例ではreadでファイルの全体をbyte配列へ一度に読み込んでいます。大きなファイルの場合は分割して読み込むなどして、OutOfMemoryErrorが出ないように工夫しましょう。

File file = new File("/A/B/C.dat");
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
	byte[] bytes = new byte[(int) file.length()];
	bis.read(bytes);
}
// NIO.2で同じことをする場合
Path file = Paths.get("/A/B/C.dat");
byte[] bytes = Files.readAllBytes(file);

2-5-2.ファイルの内容をすべて読み込む(テキストファイル)

テキストファイルを読み込む時は、java.io.FileReaderを使います。この例はListに全ての行を格納していますが、大きなファイルの場合はOutOfMemoryErrorが出ないように注意しましょう。

File file = new File("/A/B/C.txt");
List<String> lines = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
	String line = null;
	while ((line = br.readLine()) != null) {
		lines.add(line);
	}
}

ファイルのエンコーディング指定が必要なら、java.io.FileInputStreamInputStreamReaderを使うことになります。

File file = new File("/A/B/C.txt");
List<String> lines = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "MS932"))) {
	String line = null;
	while ((line = br.readLine()) != null) {
		lines.add(line);
	}
}
// NIO.2で同じことをする場合
Path file = Paths.get("/A/B/C.txt");
List<String> lines = Files.readAllLines(file); // ファイルのエンコーディングがUTF-8の場合
// String lines = Files.readString(file); // Java 11以降なら一つのStringとしても読み込める

2-5-3.ファイルへ書き込む(バイナリファイル)

バイナリデータを書き込む際は、java.io.FileOutputStreamを使います。大きなデータを出力する際は、java.io.BufferedOutputStreamを使うなどしましょう。

byte[] bytes = { 0x4a, 0x61, 0x76, 0x61 }; // "Java"
File file = new File("/A/B/C.dat");
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
	bos.write(bytes);
}
// NIO.2で同じことをする場合
byte[] bytes = { 0x4a, 0x61, 0x76, 0x61 }; // "Java"
Path file = Paths.get("/A/B/C.dat");
Files.write(file, bytes);

2-5-4.ファイルへ書き込む(テキストファイル)

テキストデータを書き込む際は、java.io.FileWriterを使います。Stringやプリミティブ型を直接扱いたい場合は、java.io.PrintWriterを使っても良いでしょう。

File file = new File("/A/B/C.txt");
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
	bw.write("Java");
	bw.newLine();
}

ファイルのエンコーディング指定が必要なら、java.io.FileOutputStreamOutputStreamWriterを使うことになります。

File file = new File("/A/B/C.txt");
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "MS932"))) {
	bw.write("Java");
	bw.newLine();
// NIO.2で同じことをする場合
List<String> lines = Arrays.asList("Java");
Path file = Paths.get("/A/B/C.txt");
Files.write(file, lines);
// Files.writeString(file, "Java"); // Java 11以降の場合

3.【参考】More New I/O APIs(NIO.2)の登場

JSR 203: More New I/O APIs for the Java Platform (“NIO.2”)」は、2011年にリリースされたJava 7で導入されました。

特徴の一つは、ファイル・ディレクトリを表すインターフェイス(java.nio.file.Path)と、ファイル操作をするためのクラス(java.nio.file.Files)が完全に分離されたことです。Filesのメソッド名も、より直感的で統一されたものになりました。

FileはJava 1.0の時代からあるクラスです。Java 1.0は、今(2018)から20年以上前に設計・実装されたものです。当時とはJavaを動かすコンピュータの性能・規模が大きく変わり、古い設計では今の環境で求められる性能を出すのが難しくなっています。

それに、従来のFileでは様々な課題がありました。UNIXでのシンボリックリンクを上手く扱えなかったり、ディレクトリツリーの探索が手軽に出来なかったり、ファイル属性の扱いやリネームや非同期I/Oが上手く出来なかったり、ファイルコピーが標準APIでは簡単にできない(!)など。その解決策がNIO.2です。

Javaプログラミングでのファイル操作の現実は、NIO.2に置き換わったとはまだ言えない状況です。でも、NIO.2Java 8以降のStream APIなどとの親和性も高く、プログラムもFileを使うよりもずっと簡単に短く書けます。ぜひNIO.2を勉強して、使いこなせるようになりましょう。


4.まとめ

この記事では、Fileの色々な使い方をざっと紹介しました。これでもFileの全てのAPIを紹介したわけではありませんので、詳細はJDKJavadoc等を参照してください。他にもいろいろ使い出があるメソッドがあります。

今のJavaのファイル操作における公式なAPIは、PathFilesを中心としたNIO.2です。Fileと比べて色々と機能が強化されていますし、便利なメソッドも多くあり、プログラム自体も短く効率的に書けます。積極的に学んで使いこなすと、周囲のプログラマーとの差がグッと付きますよ!!

私たちは、全てのエンジニアに市場価値を高め自身の望む理想のキャリアを歩んでいただきたいと考えています。もし、今あなたが転職を検討しているのであればこちらの記事をご一読ください。理想のキャリアを実現するためのヒントが見つかるはずです。

『技術力』と『人間力』を高め市場価値の高いエンジニアを目指しませんか?

私たちは「技術力」だけでなく「人間力」の向上をもって遙かに高い水準の成果を出し、関わる全ての人々に感動を与え続ける集団でありたいと考えています。

高い水準で仕事を進めていただくためにも、弊社では次のような環境を用意しています。

  • 定年までIT業界で働くためのスキル(技術力、人間力)が身につく支援
  • 「給与が上がらない」を解消する6ヶ月に1度の明確な人事評価制度
  • 平均残業時間17時間!毎週の稼動確認を徹底しているから実現できる働きやすい環境

現在、株式会社ボールドでは「キャリア採用」のエントリーを受付中です。

まずは以下のボタンより弊社の紹介をご覧いただき、あなたの望むキャリアビジョンをエントリーフォームより詳しくお聞かせください。

コメント

文系IT未経験歓迎!【23卒対象】新卒採用のエントリーを受付中!
文系IT未経験歓迎!
【23卒対象】新卒採用のエントリーを受付中!