本ページはプロモーションが含まれています

Java入門

【Java】ファイル読み込みの基本と失敗しないエラー処理

トム

・SaaS勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

Javaを学び始めると、必ずと言っていいほど「ファイルの読み込み」という壁にぶつかります。私自身、Javaエンジニアとしてキャリアをスタートしたばかりの頃、テキストファイルの内容をプログラムで扱いたいだけなのに、方法がたくさんありすぎてどれを使えば良いか分からず、半日以上悩んだ苦い経験があります。

これまで業務システムからWebサービスまで、数多くのプロジェクトでファイル入出力処理を実装してきました。その中で得た知見や、初心者がつまずきやすいポイントをたくさん見てきました。

この記事では、過去の私と同じように「Javaのファイル読み込みが分からない」と悩んでいるあなたに向けて、次の内容を徹底的に解説します。

  • ファイル読み込みの基本的な3つの方法
  • それぞれの方法のメリット・デメリット比較
  • コピペで動かせる実践的なサンプルコード
  • 初心者が必ずハマるエラーへの対処法

この記事を読み終える頃には、あなたの目的に合った最適なjava ファイル読み込みの方法が分かり、自信を持ってコードを書けるようになっているはずです。

Javaでファイルを読み込みの基本

Javaでファイル読み込みを行うには、いくつかのクラスを組み合わせて使います。ここでは、最も基本的でよく使われる3つのアプローチを紹介します。それぞれの特徴を理解して、状況に応じて使い分けられるようになりましょう。

Fileクラスを使った基本的な扱い方

まず基本となるのがjava.io.Fileクラスです。注意点として、Fileクラス自体にファイルを読み込む機能はありません

Fileクラスは、ファイルやディレクトリのパスを抽象的に表現するためのものです。ファイルが存在するかどうかを確認したり、ファイル名を取得したりする際に使います。いわば、ファイル操作の「窓口」のような役割を果たします。

import java.io.File;

public class FileExample {
    public static void main(String[] args) {
        // ファイルのパスを指定してFileオブジェクトを作成
        File file = new File("sample.txt");

        // ファイルの存在確認
        if (file.exists()) {
            System.out.println("ファイルは存在します。");
            // ファイルの絶対パスを取得
            System.out.println("パス: " + file.getAbsolutePath());
            // ファイル名を取得
            System.out.println("ファイル名: " + file.getName());
        } else {
            System.out.println("ファイルが見つかりません。");
        }
    }
}

このコードは、sample.txtというファイルが存在するかどうかをチェックするだけです。実際の読み込みは、このFileオブジェクトを他のクラス(BufferedReaderなど)と組み合わせて行います。

BufferedReaderを使った行ごとの読み込み方法

テキストファイルを1行ずつ読み込んで処理したい場合に最もよく使われるのがBufferedReaderクラスです。

BufferedReaderは、文字をバッファリング(一時的にメモリに溜め込む)しながら読み込むため、効率的に処理できます。readLine()メソッドを使うと、1行ずつ文字列として簡単に取得できるのが大きな特徴です。

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

public class BufferedReaderExample {
    public static void main(String[] args) {
        File file = new File("sample.txt");
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            String line;
            // 1行ずつ読み込み、行がなくなるまでループ
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

この例ではFileReaderと組み合わせています。FileReaderがファイルから文字を読み込み、それをBufferedReaderが効率化して1行ずつ提供するイメージです。大量の行があるファイルを扱う場合に非常に有効なjavaファイル読み込みの方法です。

Scannerを使った簡単な入力処理

Scannerクラスは、コンソールからの入力受付によく使われますが、ファイルの読み込みにも利用できます。

Scannerの便利な点は、スペースや改行などの区切り文字(デリミタ)を指定して、単語ごとにデータを読み込めることです。また、nextInt()nextDouble()といったメソッドを使えば、数値を直接読み取れるため、データ処理が簡単になります。

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

public class ScannerExample {
    public static void main(String[] args) {
        File file = new File("sample.txt");
        try (Scanner scanner = new Scanner(file)) {
            // ファイルの最後まで単語ごとに読み込む
            while (scanner.hasNext()) {
                String word = scanner.next();
                System.out.println(word);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

簡単なテキストデータや、スペース区切りのデータを手軽に扱いたい場合にはScannerが便利です。

よく使われるファイル読み込み方法の比較

ここまで紹介した基本的な方法には、それぞれ得意なことと不得意なことがあります。ここでは、それぞれの違いや、よりモダンなJavaでの書き方について見ていきましょう。

BufferedReaderとScannerの違い

BufferedReaderScannerはどちらもよく使われるため、どちらを選ぶべきか迷うかもしれません。主な違いはパフォーマンス機能性です。

項目BufferedReaderScanner
主な目的1行ずつの高速な読み込み区切り文字を使った解析的な読み込み
パフォーマンス高速。バッファリングにより効率が良い。比較的低速。正規表現による解析処理のため。
機能性readLine()による行単位の読み込みが基本。next(), nextInt()などデータ型に合わせた多彩な読み込みが可能。
スレッドセーフスレッドセーフスレッドセーフではない

結論として、単純にテキストファイルを1行ずつ読み込みたいだけであればBufferedReaderを、スペース区切りのデータや数値を直接扱いたい場合はScannerを選ぶと良いでしょう。パフォーマンスが重要視される大規模なデータ処理では、BufferedReaderの使用が推奨されます。

Filesクラス(Java 7以降)の活用

Java 7で導入されたNIO.2(New I/O 2)のFilesクラスを使うと、ファイル読み込みをさらに簡潔に記述できます。これまでのBufferedReaderなどを使った書き方よりも、はるかに少ないコード量で済むのが最大のメリットです。

例えば、ファイルの内容をすべて一度に読み込んで文字列にする場合、Files.readString()(Java 11以降)を使えばたった1行です。

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

public class FilesExample {
    public static void main(String[] args) {
        Path path = Paths.get("sample.txt");
        try {
            // ファイルの全行をリストとして読み込む (Java 8+)
            // List<String> lines = Files.readAllLines(path);
            // System.out.println(lines);

            // ファイルの全内容を1つの文字列として読み込む (Java 11+)
            String content = Files.readString(path);
            System.out.println(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

小さな設定ファイルなどを読み込む際には、このFilesクラスを使う方法が最も手軽で間違いがありません。ただし、巨大なファイルを一括で読み込むとメモリを大量に消費するため、注意が必要です。

文字コード(エンコーディング)への注意点

Windowsで作成したテキストファイルをMacやLinuxで開くと文字化けすることがあるように、Javaのファイル読み込みでも文字コード(エンコーディング)は非常に重要な問題です。

特に指定しない場合、プログラムを実行しているOSのデフォルト文字コードが使われます。しかし、環境によってUTF-8であったりShift_JISであったりするため、意図しない文字化けを引き起こす原因となります。

読み込むファイルの文字コードが分かっている場合は、必ず明示的に指定しましょう。

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class CharsetExample {
    public static void main(String[] args) {
        // Shift_JISでエンコードされたファイルを読み込む例
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream("sample_sjis.txt"), StandardCharsets.UTF_8))) { // ここではUTF-8を指定
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

InputStreamReaderを使うと、FileInputStreamで読み込んだバイトデータを、指定した文字コードで文字に変換できます。近年の開発ではUTF-8が標準ですが、古いシステムではShift_JISなどが使われていることもあるため、この方法は覚えておくと非常に役立ちます。

実践的なサンプルコード

ここからは、より具体的なシナリオを想定したJavaのファイル読み込みサンプルコードを見ていきます。

テキストファイルを一括で読み込む

設定ファイルなど、数KB程度の小さなファイルであれば、中身を一度にすべてメモリに読み込んでしまうのが最も簡単です。前述のFiles.readString()が最適です。

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

public class ReadAllText {
    public static void main(String[] args) {
        Path path = Path.of("config.txt");
        try {
            String configContent = Files.readString(path);
            System.out.println("--- 設定ファイルの内容 ---");
            System.out.println(configContent);
        } catch (IOException e) {
            System.err.println("ファイルの読み込みに失敗しました: " + e.getMessage());
        }
    }
}

この方法なら、面倒なループ処理やリソースのクローズ処理を気にする必要がありません。

CSVファイルを読み込んで処理する

カンマ区切りのCSVファイルは、データ交換のフォーマットとして頻繁に使われます。BufferedReaderで1行ずつ読み込み、Stringクラスのsplit()メソッドでカンマを基準に分割するのが基本的な処理方法です。

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

public class CsvReader {
    public static void main(String[] args) {
        String csvFile = "users.csv";
        String line;
        
        try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) {
            // ヘッダー行を読み飛ばす場合
            // br.readLine(); 

            while ((line = br.readLine()) != null) {
                String[] user = line.split(",");
                System.out.println("ID: " + user[0] + ", 名前: " + user[1] + ", メール: " + user[2]);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

このコードは、users.csvファイルを1行ずつ読み込み、各行をカンマで分割してユーザー情報を表示します。本格的なCSV処理では、データ内にカンマが含まれるケースなどを考慮したライブラリ(OpenCSVなど)を使うのが一般的ですが、簡単なものであればこの方法で十分対応できます。

大容量ファイルを効率的に扱う

数GBにもなるような大容量のログファイルなどを扱う場合、ファイル全体をメモリに読み込むのは現実的ではありません。OutOfMemoryErrorという致命的なエラーが発生してしまいます。

このようなケースでは、ストリーム処理が必須です。ストリーム処理とは、データを少しずつ読み込みながら処理を進める方式です。BufferedReaderreadLine()ループや、Java 8から導入されたFiles.lines()メソッドがこれにあたります。

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

public class LargeFileReader {
    public static void main(String[] args) {
        Path path = Path.of("large_log_file.log");
        
        // try-with-resourcesでStreamを自動的にクローズする
        try (Stream<String> lines = Files.lines(path)) {
            // "ERROR"という単語を含む行だけをカウントする
            long errorCount = lines.filter(line -> line.contains("ERROR")).count();
            System.out.println("エラー行数: " + errorCount);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Files.lines()は、ファイルを行ストリーム(Stream<String>)として扱えるようにします。メモリ消費を最小限に抑えながら、フィルタリングや集計といった高度なデータ処理を簡潔に記述できるのです。大容量ファイルを扱う際のjavaファイル読み込みでは、この方法が現在のベストプラクティスと言えるでしょう。

ファイル読み込み時のエラー処理

ファイル操作にはエラーがつきものです。ファイルが存在しなかったり、読み込み権限がなかったりと、予期せぬ事態は常に起こり得ます。堅牢なプログラムを作るためには、適切なエラー処理が不可欠です。

FileNotFoundExceptionへの対応

FileNotFoundExceptionは、その名の通り「指定されたパスにファイルが見つからない」場合に発生する、最も代表的な例外です。

この例外が発生するということは、プログラムの前提が崩れている状態です。ユーザーにファイルパスを再入力させたり、処理を中断してエラーメッセージを表示したりといった対応が必要になります。

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

public class FileNotFoundHandler {
    public static void main(String[] args) {
        File file = new File("non_existent_file.txt");
        try {
            Scanner scanner = new Scanner(file);
            // ... 読み込み処理 ...
        } catch (FileNotFoundException e) {
            System.err.println("エラー: ファイルが見つかりません。");
            System.err.println("パスを確認してください: " + file.getAbsolutePath());
        }
    }
}

catchブロックでこの例外を捕まえ、利用者に分かりやすいメッセージを出すことが重要です。

IOExceptionの基本的なハンドリング

IOExceptionは、I/O(Input/Output)操作全般で発生する可能性のある、より広範な例外です。FileNotFoundExceptionIOExceptionの一種です。

ファイル読み込み中、ディスクの故障やネットワークの問題で読み込みが中断された場合などにもIOExceptionが発生します。そのため、ファイル操作を行うコードは、基本的にIOExceptionを処理するtry-catchブロックで囲む必要があります。

try-with-resourcesを使った安全な書き方

Java 7から導入されたtry-with-resources文は、ファイル読み込みにおけるエラー処理とリソース管理を劇的に改善しました。

古いコードでは、finallyブロックでclose()メソッドを呼び出し、リソース(ファイルストリームなど)を明示的に閉じる必要がありました。このclose()処理を忘れると、ファイルハンドルが解放されずに残り、システムの不安定化を招く原因となります。

try-with-resourcesを使えば、tryブロックを抜ける際に自動的にリソースをクローズしてくれます

// 悪い例(Java 6以前の古い書き方)
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("sample.txt"));
    // ... 読み込み処理 ...
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close(); // 手動でクローズする必要がある
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 良い例(Java 7以降のモダンな書き方)
try (BufferedReader br = new BufferedReader(new FileReader("sample.txt"))) {
    // ... 読み込み処理 ...
    // tryブロックを抜ける際に自動でbr.close()が呼ばれる
} catch (IOException e) {
    e.printStackTrace();
}

コードがシンプルになるだけでなく、close()の呼び忘れも防げるため、現在のJavaではtry-with-resources文を使うのが常識です。特別な理由がない限り、必ずこの書き方を採用してください。

まとめ

今回は、Javaにおけるファイル読み込みの基本から実践的なテクニック、そして重要なエラー処理までを解説しました。最後に、ポイントを振り返ってみましょう。

目的別に使い分ける読み込み方法

  • 小さなファイルを一括で読みたい -> Files.readString()Files.readAllLines() が手軽で最適。
  • ファイルを1行ずつ処理したい -> BufferedReader を使うのが基本。パフォーマンスも良い。
  • 大容量ファイルを効率的に扱いたい -> Files.lines() を使ったストリーム処理が必須。メモリを圧迫しない。
  • スペース区切りなど簡単なデータを解析したい -> Scanner が便利。

今後学ぶべき関連API(NIO, JSON, XMLなど)

ファイル読み込みの基本をマスターしたら、次はより高度なトピックに挑戦してみましょう。

  • NIO.2 (java.nio.fileパッケージ): Filesクラス以外にも、非同期I/Oやファイルシステムの監視など、高度な機能が提供されています。
  • JSON/XMLの扱い: 現代のアプリケーションでは、設定ファイルやAPIのレスポンスとしてJSONやXML形式のデータが頻繁に使われます。JacksonやJAXBといったライブラリを使えば、これらのファイルを簡単にJavaオブジェクトにマッピングできます。

ファイルの入出力は、どんなアプリケーション開発でも避けては通れない基本的なスキルです。

  • この記事を書いた人
  • 最新記事

トム

・SaaS勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門