Java Tips | I/O・ネットワーク:ファイル読み込み

Java Java
スポンサーリンク

業務で使える「ファイル読み込みユーティリティ」とは何か

業務システムでは、設定ファイル、CSV、ログ、インポート用データなど、「ファイルを読む」場面が必ず出てきます。 毎回その場で FileInputStreamBufferedReader をゴチャゴチャ書いていると、コードが読みにくくなり、例外処理や文字コードの扱いもバラバラになります。

だからこそ、「ファイル読み込み」を小さなユーティリティとしてまとめておく価値があります。 目的は、次のようなことです。

同じパターンの読み込み処理を、毎回コピペしない。 文字コードや例外処理の方針を、プロジェクト全体で統一する。 セキュリティ的に危ない読み方(パスの扱いなど)を避ける。

ここから、初心者向けに一つずつ噛み砕いていきます。

Java の基本的なファイル読み込みの形を押さえる

「1ファイルを丸ごと文字列として読む」ユーティリティ

まずは一番よくある、「テキストファイルを全部読み込んで String にする」ユーティリティから始めます。 java.nio.file.Files を使うと、かなりシンプルに書けます。

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

public class FileReaderUtils {

    public static String readAllAsString(Path path, Charset charset) throws IOException {
        byte[] bytes = Files.readAllBytes(path);
        return new String(bytes, charset);
    }
}
Java

使い方の例です。

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

public class Example {

    public static void main(String[] args) throws Exception {
        Path path = Path.of("data/config.txt");
        String text = FileReaderUtils.readAllAsString(path, StandardCharsets.UTF_8);
        System.out.println(text);
    }
}
Java

ここで重要なのは、「文字コード(Charset)を必ず指定する」ことです。 new String(bytes) のように文字コードを省略すると、環境依存になり、文字化けや本番と検証環境の差の原因になります。 業務システムでは、ほぼ必ず UTF-8 などに統一するのがおすすめです。

行単位で読むユーティリティ(CSV や設定ファイル向け)

「1行ずつ処理したい」場合の基本形

CSV や設定ファイルなど、「行ごとに処理したい」ケースでは、Files.readAllLinesBufferedReader を使います。 ユーティリティとしてまとめると、次のような形になります。

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class LineReaderUtils {

    public static List<String> readAllLines(Path path, Charset charset) throws IOException {
        return Files.readAllLines(path, charset);
    }
}
Java

使い方の例です。

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.List;

public class LineExample {

    public static void main(String[] args) throws Exception {
        Path path = Path.of("data/users.csv");
        List<String> lines = LineReaderUtils.readAllLines(path, StandardCharsets.UTF_8);

        for (String line : lines) {
            System.out.println("LINE: " + line);
        }
    }
}
Java

ここで深掘りしたいのは、「ファイルサイズとのバランス」です。 readAllLines は、ファイル全体をメモリに載せます。 業務で扱うファイルが数十 MB 程度なら問題ありませんが、何百 MB〜GB クラスになると厳しくなります。 その場合は、次のような「ストリームで読む」形を検討します。

大きなファイルを「ストリーム」で読むユーティリティ

BufferedReader を使って行ごとに読む

巨大なファイルを扱うときは、「1行ずつ読みながら処理する」スタイルが安全です。 BufferedReader を使った基本形をユーティリティにしておきます。

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

public class StreamingLineReader {

    public interface LineHandler {
        void handle(String line) throws Exception;
    }

    public static void readLines(Path path, Charset charset, LineHandler handler) throws Exception {
        try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
            String line;
            while ((line = reader.readLine()) != null) {
                handler.handle(line);
            }
        } catch (IOException e) {
            throw e;
        }
    }
}
Java

使い方の例です。

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

public class StreamingExample {

    public static void main(String[] args) throws Exception {
        Path path = Path.of("data/large.csv");

        StreamingLineReader.readLines(path, StandardCharsets.UTF_8, line -> {
            // ここで1行ずつ処理する
            System.out.println("LINE: " + line);
        });
    }
}
Java

ここで重要なのは、「try-with-resources で必ずストリームを閉じている」ことです。 ファイルを開いたまま閉じ忘れると、リソースリーク(ファイルハンドル枯渇)につながり、長期運用で障害になります。 ユーティリティ側で try ( ... ) { ... } を徹底しておくと、呼び出し側の負担を減らせます。

パスの扱いとセキュリティの観点

Path.of を使い、相対パスと絶対パスを意識する

初心者がやりがちなのが、new File("C:\\data\\config.txt") のように、環境依存のパスをベタ書きすることです。 業務システムでは、次のような方針を決めておくと安全です。

設定ファイルやデータファイルの場所は、設定値(プロパティファイルや環境変数)から受け取る。 コードの中には「相対パス」だけを書き、ベースディレクトリは設定で決める。 Path.ofPaths.get を使い、OS 依存の区切り文字(\ /)を意識しない書き方にする。

例えば、次のようなユーティリティを用意できます。

import java.nio.file.Path;

public class AppPaths {

    private final Path baseDir;

    public AppPaths(Path baseDir) {
        this.baseDir = baseDir;
    }

    public Path config(String name) {
        return baseDir.resolve("config").resolve(name);
    }

    public Path data(String name) {
        return baseDir.resolve("data").resolve(name);
    }
}
Java

こうしておくと、「どこからファイルを読んでいるか」が明確になり、 ディレクトリトラバーサル攻撃(../../ で意図しない場所を読まれる)などを防ぎやすくなります。

例外処理をユーティリティ側でどう扱うか

IOException をそのまま投げるか、ラップするか

ファイル読み込みでは、ほぼ必ず IOException が絡みます。 ファイルが存在しない、権限がない、途中で読み込みに失敗した、などです。

ユーティリティの設計としては、次の二つの方針があります。

呼び出し側に IOException をそのまま投げて、業務ロジックでハンドリングさせる。 独自の例外(例えば FileReadException)にラップして投げ、業務側はそれだけを意識する。

初心者向けには、まず「throws IOException を付けて、呼び出し側で try-catch する」形から始めるのが分かりやすいです。

public static String readAllAsString(Path path, Charset charset) throws IOException {
    byte[] bytes = Files.readAllBytes(path);
    return new String(bytes, charset);
}
Java

呼び出し側では、こう書きます。

try {
    String text = FileReaderUtils.readAllAsString(path, StandardCharsets.UTF_8);
    // 正常処理
} catch (IOException e) {
    // ログを出す、ユーザーにエラー表示する、など
}
Java

ここで深掘りしたいのは、「例外を握りつぶさない」ことです。 catch (Exception e) {} のように何もせずに無視すると、障害の原因が分からなくなります。 最低限、ログに出す・呼び出し元に伝える、といった方針を決めておきましょう。

業務でよくある「ファイル読み込みユーティリティ」の具体例

設定ファイル(プロパティファイル)読み込み

設定ファイルを読むユーティリティは、ほぼどのシステムにもあります。 例えば、key=value 形式のシンプルな設定ファイルを読む場合です。

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;

public class PropertiesLoader {

    public static Properties load(Path path) throws IOException {
        Properties props = new Properties();
        try (InputStream in = Files.newInputStream(path)) {
            props.load(in);
        }
        return props;
    }
}
Java

使い方の例です。

Path path = Path.of("config/app.properties");
Properties props = PropertiesLoader.load(path);

String dbUrl = props.getProperty("db.url");
String dbUser = props.getProperty("db.user");
Java

ここでのポイントは、「InputStream を必ず try-with-resources で閉じている」ことと、 「Properties という標準クラスを活用している」ことです。

CSV 読み込みの前段としての「行読み込み」

CSV パーサーを使う前段として、「ファイルから行を読み込む」ユーティリティを使うのもよくあるパターンです。 先ほどの StreamingLineReader を使って、1行ずつ CSV ライブラリに渡す、などです。

セキュリティ・運用の観点から見たファイル読み込み

「どこから何を読んでいるか」を明確にする

ファイル読み込みは、そのまま「外部入力」を意味します。 設定ファイル、インポートファイル、ログなど、内容によっては機密情報や個人情報が含まれます。

だからこそ、次のような点を意識する必要があります。

ファイルパスを外部からそのまま受け取らず、許可されたディレクトリ配下に限定する。 読み込んだ内容をログに出すときは、機密情報を含まないか注意する。 権限のない場所(システムファイルなど)を読もうとしていないかチェックする。

ユーティリティ側で「ベースディレクトリを固定する」「パスの正規化を行う」などの工夫をしておくと、 業務コード側のセキュリティ負担を減らせます。

長期運用での「リソースリーク」を防ぐ

ファイル読み込みでストリームを閉じ忘れると、 短期的には問題なくても、長期運用で「ファイルハンドルが枯渇して障害になる」ということが起きます。

ユーティリティ側で try-with-resources を徹底し、 「開いたら必ず閉じる」をコードレベルで保証しておくことは、運用上とても重要です。

まとめ:ファイル読み込みユーティリティで身につけてほしい感覚

ファイル読み込みユーティリティは、「よくある読み込みパターンを、分かりやすく・安全にまとめる」ための小さな道具です。 そこには、次のような大事なポイントが詰まっています。

文字コード(Charset)を必ず指定し、環境依存を避ける。 小さいファイルは readAllBytes / readAllLines、大きいファイルはストリームで読む。 try-with-resources でストリームを必ず閉じ、リソースリークを防ぐ。 パスの扱いをユーティリティに閉じ込め、「どこから何を読んでいるか」を明確にする。 例外を握りつぶさず、ログや呼び出し元への通知をきちんと行う。

もしあなたのプロジェクトで、 毎回 new FileInputStreamBufferedReader を手書きしている場所が散らばっているなら、 それを一度「ファイル読み込みユーティリティ」に置き換えられないか眺めてみてください。

それだけで、コードの意図がはっきりし、 性能・セキュリティ・運用のバランスが取れた I/O 設計に、一歩近づきます。

タイトルとURLをコピーしました