java-recipes

ホーム ファイルI/O › F-03a

F-03a: java.io によるファイル操作(ストリーム・バイト・文字)

Java の伝統的なファイル操作API java.io を使ったテキスト・バイナリファイルの読み書きを解説します。 レガシーシステムでは現在もよく使われるパターンです。 テキスト処理では UTF-8 の正しい指定方法、バイナリコピーでのバッファリング、File クラスによるファイル情報取得を確認しましょう。

いつ使うか

  • Java 7 以前のレガシーコードを読んだり改修するとき
  • バイナリファイル(画像・PDF・ZIPなど)を読み書きするとき
  • 追記モードでログファイルに書き込みたいとき
  • File クラスでファイルの存在確認・サイズ取得・権限確認をするとき

java.io の主要クラス構成

クラス種別用途
FileInputStream / FileOutputStreamバイトストリームバイナリファイルの読み書き
InputStreamReader / OutputStreamWriter文字変換バイト→文字変換(文字コード指定)
BufferedReader / BufferedWriterバッファリング行単位の読み書き・性能向上
Fileファイル情報存在確認・サイズ・更新日時・一覧取得

サンプルコード

FileIoSample.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class FileIoSample {

    // ① テキストファイルを1行ずつ読み込む(java.io 方式)
    //    ※ FileReader は文字コードを指定できないため InputStreamReader でラップする
    public static List<String> readLines(String filePath) throws IOException {
        List<String> lines = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream(filePath), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lines.add(line);
            }
        }
        return lines;
    }

    // ② テキストファイルに書き込む
    public static void writeLines(String filePath, List<String> lines) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(
                        new FileOutputStream(filePath), StandardCharsets.UTF_8))) {
            for (String line : lines) {
                writer.write(line);
                writer.newLine(); // OS に合わせた改行コードを挿入
            }
        }
    }

    // ③ ファイルに追記する(append モード)
    public static void appendLine(String filePath, String line) throws IOException {
        // FileOutputStream の第2引数 true = 追記モード
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(
                        new FileOutputStream(filePath, true), StandardCharsets.UTF_8))) {
            writer.write(line);
            writer.newLine();
        }
    }

    // ④ バイナリファイルのコピー(FileInputStream + FileOutputStream)
    public static void copyBinary(String src, String dest) throws IOException {
        try (FileInputStream in = new FileInputStream(src);
             FileOutputStream out = new FileOutputStream(dest)) {
            byte[] buffer = new byte[8192]; // 8KB バッファ
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
    }

    // ⑤ File クラスでファイル情報を取得
    public static void printFileInfo(String filePath) {
        File file = new File(filePath);
        System.out.println("パス        : " + file.getAbsolutePath());
        System.out.println("存在        : " + file.exists());
        System.out.println("ファイル    : " + file.isFile());
        System.out.println("ディレクトリ: " + file.isDirectory());
        System.out.println("サイズ      : " + file.length() + " bytes");
        System.out.println("更新日時    : " + new java.util.Date(file.lastModified()));
        System.out.println("読取可      : " + file.canRead());
        System.out.println("書込可      : " + file.canWrite());
    }

    // ⑥ ディレクトリを作成する(親ディレクトリも含めて一括作成)
    public static boolean createDirectory(String dirPath) {
        return new File(dirPath).mkdirs();
    }

    // ⑦ ディレクトリ内のファイル一覧を取得
    public static File[] listFiles(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.isDirectory()) { return new File[0]; }
        File[] files = dir.listFiles();
        return (files != null) ? files : new File[0];
    }

    public static void main(String[] args) throws IOException {
        String tempDir = System.getProperty("java.io.tmpdir");
        String filePath = tempDir + "/fileio_sample.txt";

        // 書き込み
        List<String> lines = new ArrayList<>();
        lines.add("1行目: Hello, Java!");
        lines.add("2行目: ファイルI/Oのサンプル");
        lines.add("3行目: UTF-8 で書き込み");
        writeLines(filePath, lines);
        System.out.println("書き込み完了: " + filePath);

        // 読み込み
        List<String> readLines = readLines(filePath);
        System.out.println("読み込んだ行数: " + readLines.size());
        for (String line : readLines) {
            System.out.println("  " + line);
        }

        // 追記
        appendLine(filePath, "4行目: 追記した行");
        System.out.println("追記後の行数: " + readLines(filePath).size());

        // ファイル情報
        System.out.println("\n--- ファイル情報 ---");
        printFileInfo(filePath);

        // クリーンアップ
        new File(filePath).delete();
    }
}

Java 8 では FileReader や FileWriter は文字コードを指定できないため、InputStreamReader / OutputStreamWriter でラップして StandardCharsets.UTF_8 を指定します。これが java.io でのUTF-8対応の標準パターンです。

よくあるミス・注意点

⚠️ FileReader / FileWriter は文字コードを指定できない

new FileReader(file) は JVM のデフォルト文字コードを使います。Windows では Shift_JIS、Linux では UTF-8 になることが多く、 環境依存の文字化けが発生します。 必ず new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8) のように文字コードを明示してください。

⚠️ try-with-resources を使わないとリソースリークが起きる

FileInputStream などのストリームは必ずtry (var in = new FileInputStream(...)) { } の形で使い、close() を確実に呼んでください。 例外が発生した場合でも try-with-resources ならリソースが自動でクローズされます。

⚠️ BufferedReader なしで1文字ずつ読むと遅い

InputStreamReader を直接使って read() を呼ぶと、1文字ずつシステムコールが発生して非常に遅くなります。 必ず BufferedReader でラップしてバッファリングを有効にしてください。

テストする観点

  • 書き込んだ内容と読み込んだ内容が一致すること
  • 日本語(UTF-8)が文字化けせずに読み書きできること
  • 追記モードで書き込んだ場合、既存の内容が保持されること
  • 空のファイルを読み込んでも例外が発生せず空リストが返ること(境界値)
  • 存在しないファイルパスを読み込んだとき FileNotFoundException がスローされること(異常系)
  • バイナリコピーでコピー元とコピー先のファイルサイズが一致すること

GitHub でソースコードを見る →