Java 入門

Java ファイル入出力

前の章では、Scanner クラスを使用してコンソールとインタラクションする方法を学習しました。しかし、実務のアプリケーションでは、データは通常「ファイル」に保存されます。

本章では、Java を使用してファイルを読み書きするための基本概念を紹介します。ファイル I/O(Input/Output)の習得は、データ処理、設定管理、およびアプリケーションデータの永続化において非常に重要です。これは既存の入出力ストリームの知識をベースとしており、より高度なデータ処理テクニックを学ぶための準備となります。

1. ストリーム(Streams)の理解

Java において、ファイルの入出力は「ストリーム(Stream)」を通じて処理されます。ストリームは、連続したデータの流れを代表するものです。

  • 入力ストリーム (Input Streams):ソース(ファイルなど)からデータを読み取るために使用されます。
  • 出力ストリーム (Output Streams):目的地(ファイルなど)へデータを書き込むために使用されます。

ストリームは、データを一方通行で転送する「パイプ」のようなものだとイメージしてください。

1.1 入力ストリーム

入力ストリームを使用すると、ソースからデータを読み取ることができます。データはバイト、文字、あるいはオブジェクトなど、さまざまなフォーマットで読み取ることが可能です。Java でよく使われる入力ストリームクラスは以下の通りです。

  • FileInputStream: ファイルから生のバイトデータを読み取ります。
  • FileReader: ファイルから文字データを読み取ります。
  • BufferedReader: 文字入力ストリームからテキストを読み取り、バッファリングを行うことで、文字、配列、行の効率的な読み取りを実現します。通常は FileReader と組み合わせて使用します。

1.2 出力ストリーム

出力ストリームを使用すると、データを目的地に書き込むことができます。よく使われる出力ストリームクラスは以下の通りです。

  • FileOutputStream: 生のバイトデータをファイルに書き込みます。
  • FileWriter: 文字をファイルに書き込みます。
  • BufferedWriter: テキストを文字出力ストリームに書き込み、バッファリングを行います。通常は FileWriter と組み合わせて使用します。

2. ファイルからのデータ読み込み

開発において最も一般的なタスクである、テキストファイルの読み込みに焦点を当てます。

2.1 FileReader と BufferedReader の使用

FileReader は文字を読み取るために使用されますが、一度に1文字ずつ読み取るのは効率が低くなります。そのため、通常は BufferedReader でラップしてバッファ機能を提供します。

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

public class ReadFileExample {
    public static void main(String[] args) {
        String filePath = "data.txt"; // PC上の実際のファイルパスに置き換えてください
        
        // try-with-resources を使用してリソースを自動的にクローズする
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            // ファイルの末尾(null)に達するまで1行ずつ読み込む
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("ファイルの読み込み中にエラーが発生しました: " + e.getMessage());
        }
    }
}

ポイント解説:

  • Try-with-resources: try (BufferedReader reader = ...) という記述は、操作完了後にファイルが自動的にクローズされることを保証します。例外が発生した場合でも同様です。これはリソースリークを防ぐためのベストプラクティスです。
  • readLine(): このメソッドはテキストを1行ずつ読み取り、文字列として返します。ファイルの末尾に達すると null を返します。

2.2 Scanner によるファイルの読み込み

BufferedReader は行ごとの読み込みに適していますが、特定のフォーマット(数値など)のデータをパースする必要がある場合は、Scanner を使うと便利です。

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ReadFileScanner {
    public static void main(String[] args) {
        String filePath = "data.txt";
        try (Scanner scanner = new Scanner(new File(filePath))) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            System.err.println("ファイルが見つかりません: " + e.getMessage());
        }
    }
}

3. ファイルへのデータ書き込み

3.1 FileWriter と BufferedWriter の使用

読み込みと同様に、ファイルへの書き込み時もバッファ付きのラップクラスを使用することが推奨されます。

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

public class WriteFileExample {
    public static void main(String[] args) {
        String filePath = "output.txt";
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("これは1行目のテキストです。");
            writer.newLine(); // プラットフォームに依存しない改行コードを書き込む
            writer.write("这是2行目のテキストです。");
        } catch (IOException e) {
            System.err.println("ファイルの書き込み中にエラーが発生しました: " + e.getMessage());
        }
    }
}

3.2 ファイルへの追記(Append)

デフォルトでは、FileWriter は既存のファイル内容を上書きします。ファイルの末尾にデータを追記したい場合は、コンストラクタに true を渡す必要があります。

// 第2引数の true は追記モード(Append Mode)を有効にすることを意味します
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {
    writer.newLine();
    writer.write("これは追記されたコンテンツです。");
}

4. ベストプラクティスと注意事項

  • 必ずストリームをクローズする: システムリソースを解放するために、常にストリームを閉じることを忘れないでください。try-with-resources 構文の使用を強く推奨します。
  • 例外処理(Exception Handling): ファイル操作はエラーが発生しやすい(ファイルが存在しない、権限不足など)ため、必ず catch ブロックで IOException をキャッチしてください。
  • 適切なストリームの選択:
    • テキスト(.txt, .java, .csv)の処理:FileReader/FileWriter を使用。
    • バイナリ(画像, 動画, .exe)の処理:FileInputStream/FileOutputStream を使用。
  • バッファの使用: 読み込みでも書き込みでも、BufferedReader/BufferedWriter でラップすることで、パフォーマンスを劇的に向上させることができます。
  • ファイルパス:
    • 絶対パス: "C:/data/test.txt"
    • 相対パス: "data.txt"。これはプログラムが現在実行されているディレクトリからの相対的なパスです。