Java

【オススメあり】Javaでファイル操作する方法の徹底比較 (JDK標準 / Apache Commons / Google Guava)

この記事で分かること

  • Javaでファイル操作に使えるライブラリの一覧が欲しい (Java NIO2 / Apache Commons IO / Google Guava)
  • ライブラリの使用例、サンプルコードが見たい
  • ライブラリの速度比較結果が知りたい

この記事では、Javaでファイルの読み出し/書き込みをする際に利用できるライブラリを紹介します。

Javaではファイル操作をする方法が複数あって、どれを使えば良いか迷いますよね?
このページでは、JDKのバージョンごとにどのライブラリを選ぶのが適切か、その判断基準になる情報をまとめています。

まずは、ライブラリの一覧を示し、サンプルコード、それぞれの速度比較などを行いました。

記事の最後の「まとめ」の章には、私が実際に使用してみた感想と、どのライブラリを使えば良いか、速度比較結果などを元にオススメを記載しています。

Javaでファイル操作の方法に迷ったらぜひ参考にしてみて下さい!

ライブラリ一覧

Javaでファイル操作に使えるライブラリを紹介します。
JDKに標準で用意されているものの他、Apache commonsやGoogle Guavaのサードパーティライブラリが存在します。

これらのライブラリの中から仕様を満たすものを選んで使用すれば良いと思います。
次の章では、それぞれのライブラリの使用例をサンプルコードとして紹介します。

サンプルコード

ファイルから1行ずつメモリに読み込んで、Shift-JISに変換後、別のファイルに1行ずつ書き込むサンプルコードです。
1行ずつ処理するのは、メモリを大量に消費しないためのコツです。

一度にすべての行をメモリに読み込むメソッドも提供されていますが、ファイルサイズが大きい場合に使うのはオススメしません。(OutOfMemoryになる可能性があります)

InputStream (JDK 1.0以降)

public static void jdk1_0Style(String srcPath, String destPath) throws IOException {
    File srcFile = new File(srcPath);
    File destFile = new File(destPath);

    BufferedReader reader = null;
    OutputStream writer = null;
    try {
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(srcFile), StandardCharsets.UTF_8));
        writer = new BufferedOutputStream(new FileOutputStream(destFile));

        String line;
        while ((line = reader.readLine()) != null) { // 1行読み出し
            // プラットフォームに応じた改行文字を付加
            line += System.getProperty("line.separator");
            // Shift-JIS binary 取得
            byte[] binary = line.getBytes(Charset.forName("SJIS"));
            // ファイルに書き込み
            writer.write(binary, 0, binary.length);
        }
    } finally {
        if (writer != null) {
            writer.close();
        }
        if (reader != null) {
            reader.close();
        }
    }
}

Files (JDK 1.7以降)

public static void jdk1_7Style(String srcPath, String destPath) throws IOException {
    Path srcFile = Paths.get(srcPath);
    Path destFile = Paths.get(destPath);

    try (BufferedReader br = Files.newBufferedReader(srcFile, StandardCharsets.UTF_8)) {
        try (BufferedWriter bw = Files.newBufferedWriter(destFile, Charset.forName("SJIS"))) {
            String line;
            while ((line = br.readLine()) != null) {
                // ファイルに書き込み
                bw.write(line + System.lineSeparator());
            }
        }
    }
}

Files#lines (JDK1.8以降)

ファイルの行をstreamとして扱いたい場合、linesメソッドを使います。

public static void jdk1_8Style(String srcPath, String destPath) throws IOException {
    Path srcFile = Paths.get(srcPath);
    Path destFile = Paths.get(destPath);

    try (BufferedReader br = Files.newBufferedReader(srcFile, StandardCharsets.UTF_8)) {
        try (BufferedWriter bw = Files.newBufferedWriter(destFile, Charset.forName("SJIS"))) {
            br.lines()
                    .map(s -> s + System.lineSeparator())
//                  .forEach(bw::write) // 例外が出なければこれで良いのだが
                    .forEach(s -> {
                        try {
                            // ファイルに書き込み
                            bw.write(s);
                        } catch (IOException e) {
                            // 例外処理をしないといけないのがイマイチ
                            e.printStackTrace();
                        }
                    });
        }
    }
}

FileUtils (Apache commons IO)

public static void apacheCommonsStyle(String srcPath, String destPath) throws IOException {
    File srcFile = new File(srcPath);
    File destFile = new File(destPath);

    // LineIterator を使う場合
    try (LineIterator it = FileUtils.lineIterator(srcFile, "UTF-8")) {
        try (FileOutputStream out = FileUtils.openOutputStream(destFile)) {
            while (it.hasNext()) {
                String line = it.nextLine() + System.lineSeparator();
                // ファイルに書き込み
                out.write(line.getBytes(Charset.forName("SJIS")));

                // FileUtils.write(destFile, line, "SJIS", true); // 毎回ファイルオープンするので非常に低速
            }
        }
    }

    // BufferedReader / BufferedOutputStream を使う場合 (apache commons)
    try (BufferedReader br = new BufferedReader(new InputStreamReader(FileUtils.openInputStream(srcFile), StandardCharsets.UTF_8))) {
        try (BufferedOutputStream out = new BufferedOutputStream(FileUtils.openOutputStream(destFile))) {
            String line;
            while ((line = br.readLine()) != null) {
                line += System.lineSeparator();
                // ファイルに書き込み
                out.write(line.getBytes(Charset.forName("SJIS")));
            }
        }
    }
}

maven repository

Maven Repository: commons-io » commons-io (mvnrepository.com)

Files (Google Guava)

public static void googleGuavaStyle(String srcPath, String destPath) throws IOException {
    File srcFile = new File(srcPath);
    File destFile = new File(destPath);

    // Source file
    CharSource charSource = Files.asCharSource(srcFile, StandardCharsets.UTF_8);
    // Dest file
    CharSink charSink = Files.asCharSink(destFile, Charset.forName("SJIS"));
    // ファイルの中身をコピーする
    charSource.copyTo(charSink);
}

maven repository

Maven Repository: com.google.guava » guava (mvnrepository.com)

速度比較結果

ライブラリごとの速度比較をしました。
Apache Commons IOは、LineIterator を使うパターンで計測しています。

テストデータは100万行のCSVファイルで、それぞれ2回ずつ計測しました。

JDK 1.0: 654 (msec)
JDK 1.7: 554 (msec)
JDK 1.8: 505 (msec)
Apache Commons IO: 2744 (msec)
Google Guava: 434 (msec)
----------------------------------
JDK 1.0: 672 (msec)
JDK 1.7: 525 (msec)
JDK 1.8: 486 (msec)
Apache Commons IO: 2647 (msec)
Google Guava: 410 (msec)

速度は僅差でGoogle Guavaを使ったコードが一位
BufferedReader/Writer を使うパターンでは、どの書き方でもそれほど大きな差は無かったです。
Apache Commons IOだけ4倍ほど時間が掛かっていたので、BufferedReaderを使う方法に書き直したら他と同程度の速度にはなりました。

まとめ

最後に、この記事の内容をまとめます。

要点まとめ

  • BufferedReaderを使うパターンでは速度の差はあまり無い
  • JDK 1.0の書き方は可読性が悪いので、使用するのはレガシーシステムなどの場合のみ
  • JDK 1.7の書き方は追加のライブラリが不要で、可読性も良い
  • JDK 1.8のstreamはfilterとかmapを使いたい場合には便利かもしれない。ただし例外が発生する処理があると可読性が悪くなる
  • Apache Commons IOは、1行ずつ処理するのに向いていない。別途ライブラリの導入が必要。他の便利なメソッドを使いたい場合に採用する
  • Google Guavaは、コードが一番シンプルになる。僅かに速い。別途ライブラリの導入が必要。機能性ではApache Commons IOに負ける。(オススメ)

ファイルを一行ずつ読み込んで、文字コード変換したものをファイルに書き込むサンプルコードを元に速度の比較を行いました。

個人的には、Google Guavaがシンプルに書けて速度も速いのでオススメですが、
別途ライブラリの導入が必要ないJDK 1.7の書き方も無難な選択ではあると思います。

これら2つの方法で実装できない機能がある場合、以下のやり方も検討するのが良いと思います。

JDK 1.8のstreamで取り扱う方法もやりたい事次第では、綺麗なコードが書けそうな気がします。

Apache Commons IOは、ファイルのRead/Writeより、ファイル単位でCopyしたりCreateDirectoryしたりするのに便利なメソッドがあるので、そういう用途の場合に使う方が良いのではないかと思いました。

少しでも参考になりましたら幸いです。

この記事は役に立ちましたか?

  • この記事を書いた人
アバター画像

ゴイチ

ソフトウェアエンジニア歴20年。 C/C++、Java、C#、Kotlinが得意で、組込系からAndroidアプリ、大規模なWebサービスなど幅広いプログラミング経験があります。 現在は某SNSの会社でWebエンジニアをしています。

-Java
-,