Javaファイル入出力完全ガイド

2025-08-02

はじめに

Javaプログラミングにおいて、ファイルの読み書きはデータ永続化や外部システムとの連携に欠かせないスキルです。この記事では、Javaのファイル入出力(I/O)について、基礎から実践的なテクニックまでを網羅的に解説します。すでにJavaの基本文法や例外処理を理解していることを前提に、ファイル操作に特化した内容をお届けします。

ファイル入出力を学ぶメリット:

  • データを永続的に保存できる
  • 外部システムとデータを交換可能
  • 設定ファイルやログファイルを扱える
  • 大規模データを効率的に処理できる

Java I/Oの基本概念

ストリームとは?

JavaのI/O操作は「ストリーム」という概念を中心に設計されています。ストリームとは、データの流れを抽象化したもので、以下の2種類があります:

  1. バイトストリーム – バイト単位のデータ処理(画像、実行ファイルなど)
  2. 文字ストリーム – 文字単位のデータ処理(テキストファイルなど)

主要なI/Oクラス階層

InputStream (バイト入力)
├── FileInputStream
├── BufferedInputStream
└── ObjectInputStream

OutputStream (バイト出力)
├── FileOutputStream
├── BufferedOutputStream
└── ObjectOutputStream

Reader (文字入力)
├── InputStreamReader
├── FileReader
└── BufferedReader

Writer (文字出力)
├── OutputStreamWriter
├── FileWriter
└── BufferedWriter

図:Java I/Oの主要クラス階層(簡略版)

テキストファイルの読み書き

ファイル読み込みの基本

方法1: FileReader + BufferedReader (従来型)

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TextFileReader {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("ファイル読み込みエラー: " + e.getMessage());
        }
    }
}

方法2: Files.readAllLines (Java 7以降)

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class ModernTextFileReader {
    public static void main(String[] args) {
        try {
            List lines = Files.readAllLines(Paths.get("example.txt"));
            lines.forEach(System.out::println);
        } catch (IOException e) {
            System.err.println("ファイル読み込みエラー: " + e.getMessage());
        }
    }
}

方法3: Files.lines (Java 8以降, ストリームAPI利用)

import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.stream.Stream;

public class StreamFileReader {
    public static void main(String[] args) {
        try (Stream lines = Files.lines(Paths.get("example.txt"))) {
            lines.forEach(System.out::println);
        } catch (IOException e) {
            System.err.println("ファイル読み込みエラー: " + e.getMessage());
        }
    }
}

ファイル書き込みの基本

方法1: FileWriter + BufferedWriter (従来型)

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class TextFileWriter {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, Java IO!");
            writer.newLine();
            writer.write("2行目です");
        } catch (IOException e) {
            System.err.println("ファイル書き込みエラー: " + e.getMessage());
        }
    }
}

方法2: Files.write (Java 7以降)

import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class ModernTextFileWriter {
    public static void main(String[] args) {
        List lines = Arrays.asList("1行目", "2行目", "3行目");
        try {
            Files.write(Paths.get("output.txt"), lines);
        } catch (IOException e) {
            System.err.println("ファイル書き込みエラー: " + e.getMessage());
        }
    }
}

バイナリファイルの読み書き

バイナリファイル読み込み

import java.io.FileInputStream;
import java.io.IOException;

public class BinaryFileReader {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("image.jpg"))) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                // バッファ内のデータを処理(bytesReadは実際に読み込んだバイト数)
                processData(buffer, bytesRead);
            }
        } catch (IOException e) {
            System.err.println("ファイル読み込みエラー: " + e.getMessage());
        }
    }

    private static void processData(byte[] data, int length) {
        // バイナリデータ処理の例
        System.out.println(length + "バイト処理しました");
    }
}

バイナリファイル書き込み

import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryFileWriter {
    public static void main(String[] args) {
        byte[] data = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello"のASCIIコード
        try (FileOutputStream fos = new FileOutputStream("binary.bin"))) {
            fos.write(data);
        } catch (IOException e) {
            System.err.println("ファイル書き込みエラー: " + e.getMessage());
        }
    }
}

ファイルとディレクトリ操作

ファイル・ディレクトリ情報の取得

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;

public class FileInfo {
    public static void main(String[] args) {
        Path path = Paths.get("example.txt");

        System.out.println("ファイル名: " + path.getFileName());
        System.out.println("親ディレクトリ: " + path.getParent());
        System.out.println("絶対パス: " + path.toAbsolutePath());

        try {
            System.out.println("サイズ: " + Files.size(path) + "バイト");
            System.out.println("最終更新日時: " + Files.getLastModifiedTime(path));
            System.out.println("読み取り可能? " + Files.isReadable(path));
            System.out.println("通常ファイル? " + Files.isRegularFile(path));
        } catch (IOException e) {
            System.err.println("エラー: " + e.getMessage());
        }
    }
}

ファイル・ディレクトリの作成・削除

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;

public class FileDirectoryOperations {
    public static void main(String[] args) {
        // ディレクトリ作成
        Path dir = Paths.get("new_directory");
        try {
            if (!Files.exists(dir)) {
                Files.createDirectory(dir);
                System.out.println("ディレクトリ作成: " + dir);
            }

            // ファイル作成
            Path file = dir.resolve("test.txt"); // ディレクトリ内にパスを解決
            if (!Files.exists(file)) {
                Files.createFile(file);
                System.out.println("ファイル作成: " + file);
            }

            // 削除
            Files.deleteIfExists(file);
            System.out.println("ファイル削除: " + file);

            // ディレクトリ削除(空でないと削除できない)
            Files.deleteIfExists(dir);
            System.out.println("ディレクトリ削除: " + dir);

        } catch (IOException e) {
            System.err.println("操作エラー: " + e.getMessage());
        }
    }
}

高度なファイル操作

ファイルのコピーと移動

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.nio.file.StandardCopyOption;

public class FileCopyMove {
    public static void main(String[] args) {
        Path source = Paths.get("source.txt");
        Path targetCopy = Paths.get("copy_of_source.txt");
        Path targetMove = Paths.get("moved_source.txt");

        try {
            // ファイルコピー(既存ファイルは上書き)
            Files.copy(source, targetCopy, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("ファイルコピー完了");

            // ファイル移動(リネーム)
            Files.move(source, targetMove, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("ファイル移動完了");

        } catch (IOException e) {
            System.err.println("操作エラー: " + e.getMessage());
        }
    }
}

ディレクトリ走査

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.stream.Stream;

public class DirectoryWalker {
    public static void main(String[] args) {
        Path startDir = Paths.get(".");

        // 方法1: Files.walk (再帰的)
        try (Stream paths = Files.walk(startDir)) {
            System.out.println("--- すべてのファイルとディレクトリ ---");
            paths.forEach(System.out::println);
        } catch (IOException e) {
            System.err.println("走査エラー: " + e.getMessage());
        }

        // 方法2: Files.list (非再帰的)
        try (Stream paths = Files.list(startDir)) {
            System.out.println("--- 直下のファイルとディレクトリ ---");
            paths.forEach(System.out::println);
        } catch (IOException e) {
            System.err.println("走査エラー: " + e.getMessage());
        }
    }
}

オブジェクトのシリアライズ

Javaオブジェクトをファイルに保存・読み込みする方法です。

オブジェクトの書き込み

import java.io.*;
import java.util.ArrayList;
import java.util.List;

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class ObjectSerialization {
    public static void main(String[] args) {
        List people = new ArrayList<>();
        people.add(new Person("山田太郎", 25));
        people.add(new Person("佐藤花子", 30));

        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("people.dat"))) {
            oos.writeObject(people);
            System.out.println("オブジェクト書き込み完了");
        } catch (IOException e) {
            System.err.println("シリアライズエラー: " + e.getMessage());
        }
    }
}

オブジェクトの読み込み

import java.io.*;
import java.util.List;

public class ObjectDeserialization {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("people.dat"))) {
            List people = (List) ois.readObject();
            System.out.println("読み込んだオブジェクト:");
            people.forEach(System.out::println);
        } catch (IOException | ClassNotFoundException e) {
            System.err.println("デシリアライズエラー: " + e.getMessage());
        }
    }
}

実践的なファイル処理例

ログファイルの解析

import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class LogAnalyzer {
    public static void main(String[] args) {
        String logFile = "server.log";

        try (Stream lines = Files.lines(Paths.get(logFile))) {
            // エラーレベルのログをカウント
            Map errorCounts = lines
                .filter(line -> line.contains("ERROR"))
                .map(line -> line.split(" ")[0]) // 日付部分を取得
                .collect(Collectors.groupingBy(
                    date -> date,
                    Collectors.counting()
                ));

            System.out.println("エラー発生状況:");
            errorCounts.forEach((date, count) -> 
                System.out.println(date + ": " + count + "件"));

        } catch (IOException e) {
            System.err.println("ログ解析エラー: " + e.getMessage());
        }
    }
}

設定ファイルの読み込み

# config.properties
database.url=jdbc:mysql://localhost:3306/mydb
database.user=admin
database.password=secret
app.timeout=5000
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class ConfigLoader {
    public static void main(String[] args) {
        Properties props = new Properties();

        try (FileInputStream fis = new FileInputStream("config.properties")) {
            props.load(fis);

            String dbUrl = props.getProperty("database.url");
            String dbUser = props.getProperty("database.user");
            String dbPassword = props.getProperty("database.password");
            int timeout = Integer.parseInt(props.getProperty("app.timeout"));

            System.out.println("DB接続情報:");
            System.out.println("URL: " + dbUrl);
            System.out.println("ユーザー: " + dbUser);
            System.out.println("タイムアウト: " + timeout + "ms");

        } catch (IOException e) {
            System.err.println("設定ファイル読み込みエラー: " + e.getMessage());
        }
    }
}

ファイル入出力のベストプラクティス

1. リソース管理

  • try-with-resourcesを使用して確実にリソースを解放
  • ストリームは使用後すぐに閉じる

2. エンコーディングの明示

// 悪い例(プラットフォーム依存のデフォルトエンコーディング)
new FileReader("file.txt");

// 良い例(明示的なエンコーディング指定)
new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);

3. バッファリングの活用

// 非効率的
try (FileReader fr = new FileReader("large.txt")) {
    int c;
    while ((c = fr.read()) != -1) { // 1文字ずつ読み込み
        // 処理
    }
}

// 効率的
try (BufferedReader br = new BufferedReader(new FileReader("large.txt"))) {
    String line;
    while ((line = br.readLine()) != null) { // 行単位で読み込み
        // 処理
    }
}

4. 大規模ファイル処理

// メモリ効率の悪い方法(小~中規模ファイル向け)
List lines = Files.readAllLines(Paths.get("huge_file.txt"));

// メモリ効率の良い方法(大規模ファイル向け)
try (Stream lines = Files.lines(Paths.get("huge_file.txt"))) {
    lines.forEach(line -> {
        // 行ごとの処理
    });
}

5. 一時ファイルの安全な使用

import java.nio.file.Files;
import java.nio.file.Path;

public class TempFileExample {
    public static void main(String[] args) {
        try {
            // 一時ファイル作成
            Path tempFile = Files.createTempFile("prefix_", ".suffix");
            System.out.println("一時ファイル: " + tempFile);

            // 使用後削除(JVM終了時にも自動削除)
            tempFile.toFile().deleteOnExit();

            // 一時ファイルを使用...

        } catch (IOException e) {
            System.err.println("一時ファイル作成エラー: " + e.getMessage());
        }
    }
}

よくあるエラーと対処法

1. FileNotFoundException

原因: ファイルが存在しない、パスが間違っている、アクセス権限がない
対処法:

Path path = Paths.get("file.txt");
if (!Files.exists(path)) {
    System.err.println("ファイルが見つかりません: " + path);
    return;
}

2. AccessDeniedException

原因: ファイルへのアクセス権限がない
対処法: 権限を確認し、必要に応じて変更

3. 文字化け

原因: エンコーディングの不一致
対処法: 明示的にエンコーディングを指定

Files.readAllLines(path, StandardCharsets.UTF_8);

4. OutOfMemoryError

原因: 巨大なファイルを一気に読み込もうとした
対処法: ストリーム処理またはバッファリングを使用

練習問題

理解を深めるための練習問題を用意しました。実際にコードを書いて試してみましょう。

問題1: ファイルコピーツール

コマンドライン引数で指定されたソースファイルをターゲットファイルにコピーするプログラムを作成してください。適切なエラーハンドリングを含めてください。

問題2: 単語カウントプログラム

テキストファイルを読み込み、各単語の出現回数をカウントして結果を表示するプログラムを作成してください。

問題3: 設定ファイルジェネレータ

キーと値のペアをプロパティファイル形式で保存するプログラムを作成してください。既存のファイルがある場合は追記できるようにしてください。

問題4: ディレクトリ同期ツール

2つのディレクトリを比較し、片方にしかないファイルや更新日時が異なるファイルをリストアップするプログラムを作成してください。

問題5: CSVファイルパーサー

CSVファイルを読み込み、各行をオブジェクトに変換して処理するプログラムを作成してください。ヘッダー行がある場合とない場合の両方に対応させてください。

次のステップ

ファイル入出力の基礎を理解したら、以下のトピックに進みましょう:

  1. NIO.2 API – より高度なファイルシステム操作
  2. 非同期I/O – ノンブロッキングなファイル操作
  3. Zipファイル処理 – 圧縮ファイルの取り扱い
  4. ネットワークプログラミング – ソケット通信

さらに学ぶためのリソース:

まとめ

この記事では、Javaのファイル入出力について包括的に解説しました。重要なポイントをまとめます:

  1. テキストファイル処理 – Reader/WriterクラスとFilesユーティリティ
  2. バイナリファイル処理 – InputStream/OutputStreamクラス
  3. ファイルシステム操作 – ファイル・ディレクトリの作成/削除/コピー
  4. オブジェクトシリアライズ – オブジェクトの永続化
  5. ベストプラクティス – リソース管理、エンコーディング指定、バッファリング

ファイル操作はJavaプログラミングの基本スキルです。安全で効率的なコードを書くために、適切な例外処理とリソース管理を常に心がけましょう。最初はシンプルなファイル操作から始め、徐々に複雑な処理に挑戦していくのがおすすめです。Happy coding!