1. 上書きでの書き込み
ファイルを扱うとき、「ファイルに書き込む」といっても常に同じ意味ではありません。あるときはファイルを完全に上書きする(新しいレポートを保存するときなど)。別のときは既存ファイルの末尾に新しい情報を追加する(アプリのイベントログを追記するなど)。また、内容を変更せずにファイルを読むだけでよい場合もあります。
Java(特にモダンな java.nio.file パッケージ)では、書き込みメソッドに渡すオプションの組み合わせでファイル操作モードを指定します。例えば Files.write() にオプションを渡すことで、上書きしたいのか末尾に追加したいのかを明示できます。
仕組み
Files.write(path, data) を呼ぶと、Java は既定で新しいファイルを作成するか、既存ファイルを上書きします。古い内容はすべて消え、そこに新しいデータだけが残ります。
この既定の挙動は、ファイルの「ハードリセット」のようなものです。ファイルに以前 1000 行あっても、1 行だけを書き込めば、それまでの内容は跡形もなく消えます。
例: 文字列をファイルに書き込む
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class OverwriteFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> lines = List.of("こんにちは、世界!", "これはファイルへの新しい書き込みです。");
// 行をファイルに書き込みます(古い内容は削除されます)
Files.write(path, lines);
System.out.println("ファイルを正常に上書きしました!");
}
}
このコードを実行すると、ファイル myfile.txt には lines の 2 行だけが残ります。以前の内容は(Java による「ためらい」なしに)消去されます。
2. 追記 (append): ファイル末尾にデータを追加
いくつかの用途(イベントログの記録、ログ出力、データの蓄積など)では、古い内容を消さずに末尾へ新しい行を追加したいことがあります。古い API では FileWriter にフラグ append = true を渡すのが一般的でした。モダンな API では、より簡潔かつ明示的に行えます。
やり方
必要なのは、メソッド Files.write() にオプション StandardOpenOption.APPEND を渡すことだけです:
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class AppendFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> moreLines = List.of("もう 1 行を追加しました。", "さらにもう 1 行!");
// ファイルの末尾に行を追加します(既存の内容は保持されます)
Files.write(path, moreLines, StandardOpenOption.APPEND);
System.out.println("行をファイルの末尾に追加しました!");
}
}
重要なポイント: ファイルが存在しない場合、追記しようとするとエラーになります。APPEND モードでは Java は自動でファイルを作成しません。存在しなければ作成したい場合は、次の 2 つのオプションを同時に使います:
Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
実際の挙動
- 上書きで書き込んで実行すると、ファイルには 2 行だけが入ります。
- 追記で実行すると、その 2 行の末尾に新しい行が追加され、元の内容は残ります。
3. オプションの組み合わせ: CREATE, APPEND, TRUNCATE_EXISTING
Java では、複数のオプションを組み合わせて、ファイルのオープンモードを柔軟に制御できます:
- StandardOpenOption.CREATE — 存在しない場合にファイルを作成する。
- StandardOpenOption.APPEND — 末尾にデータを追加する。
- StandardOpenOption.TRUNCATE_EXISTING — 既存ファイルがある場合は長さを 0 にして(空にして)から書く。
- StandardOpenOption.CREATE_NEW — 新規作成専用。すでに存在する場合はエラー。
例: 存在しなければ作成し、末尾に追加
Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
例: ファイルを作成するか、完全に空にしてから新しいデータを書き込む
Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
表: 主な書き込みモード
| オプション | 動作 |
|---|---|
| (デフォルト) | ファイルを新規作成、または既存ファイルを上書き |
|
末尾に追加。ファイルがないとエラー |
|
末尾に追加。なければ作成 |
|
ファイルを空にしてから新しいデータを書き込み |
|
新規作成専用。既存ならエラー |
4. バイナリファイルの読み書き
ここまでは文字列を扱ってきましたが、画像・アーカイブ・PDF など「生」のバイトを読み書きしたい場合もあります。そんなときは次を使います:
- Files.readAllBytes(path) — ファイルをバイト配列に読み込む。
- Files.write(path, byteArray) — バイト配列をファイルに書き込む。
例: ファイルのコピー
import java.nio.file.*;
import java.io.IOException;
public class CopyBinaryFileExample {
public static void main(String[] args) throws IOException {
Path source = Paths.get("logo.png");
Path target = Paths.get("logo_copy.png");
byte[] data = Files.readAllBytes(source);
Files.write(target, data);
System.out.println("ファイルをコピーしました!");
}
}
例: バイナリデータを追記
byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
注意: 構造化データ(画像など)が既に入っているファイルにバイナリを追記すると、多くの場合ファイルを破損させます。追記は、テキストや、追記を前提として設計されたバイナリ(ログなど)に対してのみ使いましょう。
5. いつストリームを使うべきか(FileInputStream、BufferedReader など)
Files.readAllBytes() や Files.write() は、小〜中規模のファイルに便利で、内容を一度にメモリへ読み込める場合に適しています。ファイルが大きい(例えば数 GB)場合や、ログ解析のように 1 行ずつ読みたい場合は、ストリームを使いましょう。
BufferedReader でファイルを行単位に読む例
import java.nio.file.*;
import java.io.*;
public class ReadLinesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedReader reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("行: " + line);
}
}
}
}
BufferedWriter で行単位に書き込み、かつ追記する例
import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class WriteAppendExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedWriter writer = Files.newBufferedWriter(
path,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND)) {
writer.write("BufferedWriter でもう 1 行!");
writer.newLine();
}
}
}
6. 実践: ファイルを作成し、そこにタスクを追記する
ここまでを 1 つのアプリにまとめてみましょう。テキストファイルに ToDo リストを保存するプログラムを作ります。ユーザーが新しいタスクを追加するたびに、ファイルの末尾へ追記します。
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class TodoList {
public static void main(String[] args) throws IOException {
Path path = Paths.get("todo.txt");
Scanner scanner = new Scanner(System.in);
System.out.print("新しいタスクを入力してください: ");
String task = scanner.nextLine();
// タスクをファイル末尾に追記します(ファイルがなければ作成)
Files.write(path,
List.of(task),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
System.out.println("タスクを追加しました!");
}
}
このプログラムを複数回実行すると、追加したタスクが毎回新しい行としてファイルに追記されます。これで簡単な「永続」ToDo リストの完成です。
7. エラー処理: 何が問題になりうるか?
ファイル操作には常にエラーの可能性が伴います。例えば:
- IOException — 入出力全般の基底例外。他のプログラムにファイルが使用中、読み書きの権限なし、ディスク満杯などで発生します。
- NoSuchFileException — 存在しないファイルを読み取ろうとしたり追記しようとした場合(かつ CREATE を指定していない場合)。
- FileAlreadyExistsException — CREATE_NEW を使っていて、すでにファイルが存在する場合。
推奨: ファイル操作は常に try-catch で囲みましょう:
try {
// ファイル操作
} catch (IOException e) {
System.out.println("ファイル操作中のエラー: " + e.getMessage());
}
8. ファイルオープンモードでありがちなミス
ミス1: 追記時に CREATE を付け忘れる。 まだ存在しないファイルに対して APPEND で追記しようとすると、NoSuchFileException になります。ファイルが自動作成されることを期待するなら、必ず CREATE を併用しましょう。
ミス2: 意図せぬ上書き。 追加ではなく Files.write(path, data) をオプションなしで呼ぶと、既存の内容はすべて消えます。追記したい場合は APPEND を使いましょう。
ミス3: バイナリとテキストを同じファイルで混在させる。 テキストとバイナリを混ぜると、たいてい読み取れないファイルになります。1 つのファイルでは 1 つの形式に統一しましょう。
ミス4: ストリームを閉じ忘れる。 BufferedWriter や BufferedReader などのストリームを使う場合は、必ず閉じましょう(try-with-resources を推奨)。閉じないとファイルがロックされたままになることがあります。
ミス5: 例外を処理しない。 ファイル操作はいつでも IOException を投げ得ます。例外を処理しないと、プログラムは異常終了します。
GO TO FULL VERSION