📁

入門Javaのファイル操作

に公開

環境

JDK 25

Java 8以降であれば、多少バージョンが違っていても同じだと思います。

主要なクラス・インタフェース

ファイル操作に関するクラス・インタフェースの多くはjava.nio.fileパッケージに属しています。

これらのクラス・インタフェースを総称して「NIO.2」と呼ばれます。

Pathインタフェース

Pathインタフェースは、名前の通りファイルやディレクトリのパスを表します。Path.of("ファイルやディレクトリのパス")Pathオブジェクトを生成できます。

Path targetFile = Path.of("/tmp/test/sample.txt");

Filesクラス

ファイルまたはディレクトリに関する操作(読み込みや書き込みなど)はFilesクラスのstaticメソッドとしてまとまっています。

いろんなメソッドが用意されているので、ぜひJavadocを一通り眺めてみてください!

文字コード

文字コードはCharset抽象クラスが表します。Charset.forName("文字コード名")で、指定した文字コードを表すCharsetオブジェクトを生成できます。

MS932(Windowsデフォルトの文字コード)を取得する例
Charset cs = Charset.forName("MS932");
EUC-JPを取得する例
Charset cs = Charset.forName("EUC-JP");

文字コード名が正しくない場合はUnsupportedCharsetExceptionがスローされます。

UTF-8やUTF-16などを指定する場合はStandardCharsetsクラスの定数を利用できます。

Charset cs = StandardCharsets.UTF_8;
Charset cs = StandardCharsets.UTF_16;

テキストファイルの読み込み

/tmp/test/sample.txt(読み込み対象)
あいうえお
かきくけこ
さしすせそ

1行ずつ読み込んで加工する

Files.lines()メソッドを利用すると、各行を要素に持つStream<String>を取得できます。

今回は各行を[]で囲うように変換します。

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

public class Main {
    public static void main(String[] args) {
        Path targetFile = Path.of("./test/sample.txt");
        // 処理終了後に自動的にStreamがclose()されるようにtry-with-resourcesを利用
        try (Stream<String> stream = Files.lines(targetFile, StandardCharsets.UTF_8)) {
            // 各行を[]で囲って、標準出力に出力
            stream.map(line -> "[" + line + "]")
                    .forEach(line -> System.out.println(line));
        } catch (IOException e) {
            // 業務コードでe.printStackTrace()を使うことはめったにありません。
            // 業務では各プロジェクトのルールに合わせて、例外の再スローやログの出力などを行ってください。
            e.printStackTrace();
        }
    }
}
実行結果
[あいうえお]
[かきくけこ]
[さしすせそ]

Stream<String>に対してtoList()すると、ファイルの内容を全て保持するList<String>を作成してしまい、それによってメモリを圧迫してOutOfMemoryErrorを引き起こす可能性があります。

上記のコードのようにStream<String>のまま扱うと、ファイルの1行ずつがメモリに乗る+必要に応じてガベージコレクションが実行されるため、メモリを圧迫しません。

ガベージコレクションについては、👇️の記事を読んでみてください。

https://gihyo.jp/dev/serial/01/jvm-arc/0002

引数がPathのみで文字コードを指定しないlines()メソッドもあります(文字コードはUTF-8になります)。実際にコードを書く場合は、明示的に文字コードを指定するlines()を使ったほうがいいでしょう。

何行か飛ばす、指定した行数だけ読み込む

サイズが大きいファイルを読み込んでList<String>を作りたい場合は、何回かに分けてファイルを読むことをおすすめします。

Streamskip()メソッドで飛ばす行数を、limit()メソッドで読み込む行数を指定できます。

        try (Stream<String> stream = Files.lines(targetFile, StandardCharsets.UTF_8)) {
            // 1000行を持つリストになる
            List<String> list = stream.map(line -> "[" + line + "]")
                    .skip(3000)  // 3000行飛ばす
                    .limit(1000)  // 1000行読み込む
                    .toList();

全行を保持するListを生成する

テキストファイルのサイズが実行環境のメモリ量より十分に小さい場合、および将来もそのテキストファイルが大きくならないことが分かっている場合は、Files.readAllLines()を利用してファイルの内容を全て保持するList<String>に変換できます。

今は小さなファイルであっても、時が経つにつれてデータ量が多くなり、ファイルが大きくなることがあります。しっかりと検討してください。

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

public class Main {
    public static void main(String[] args) {
        Path targetFile = Path.of("/tmp/test/sample.txt");
        try {
            // 1要素=1行であるListを生成する
            List<String> lines = Files.readAllLines(targetFile, StandardCharsets.UTF_8);
            for (String line : lines) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
実行結果
あいうえお
かきくけこ
さしすせそ

ファイルの全内容を1つの文字列として取得する

Files.readString()を利用します。

このメソッドも、ファイルの全内容を一気に読み込むので注意してください。

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

public class Main {
    public static void main(String[] args) {
        Path targetFile = Path.of("/tmp/test/sample.txt");
        try {
            String content = Files.readString(targetFile, StandardCharsets.UTF_8);
            System.out.println(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
実行結果
あいうえお
かきくけこ
さしすせそ

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

/tmp/test/sample.txt(書き込み対象)
あいうえお
かきくけこ
さしすせそ

StandardOpenOption

StandardOpenOption列挙型は、ファイルの開き方および作成方法を表します。

後述するFilesクラスの各メソッドでは、最後の引数にStandardOpenOptionを指定します(可変長引数なので複数指定可能)。

  • CREATE
    • ファイルが存在しない場合は新規作成します。
    • ファイルが存在する場合は何もしません。
  • CREATE_NEW
    • ファイルが存在しない場合は新規作成します。
    • ファイルが存在する場合は例外がスローされます。
  • APPEND
    • 既存の内容の最後に追記します。
  • TRUNCATE_EXISTING
    • 既存の内容を上書きします。

書き込みの実行

書き込みにはBufferedWriterを利用します。

Files.write()でも書き込みを行えます。しかしこのメソッドは改行コードがOSに依存します(Mac/LinuxではLF、WindowsではCRLF)。改行コードは明示的に指定したほうが良いので、BufferedWriterをおすすめします。

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Path targetFile = Path.of("/tmp/test/sample.txt");
        try (BufferedWriter bufferedWriter = Files.newBufferedWriter(targetFile,
                StandardCharsets.UTF_8,
                // ファイルが存在しない場合は新規作成
                StandardOpenOption.CREATE,
                // ファイルが存在する場合は追記
                //(上書きの場合はTRUNCATE_EXISTINGに変更する)
                StandardOpenOption.APPEND
        )) {
            List<String> lines = List.of("たちつてと", "なにぬねの", "はひふへほ");
            for (String line : lines) {
                bufferedWriter.write(line);
                bufferedWriter.write("\r\n");  // 改行コードにCRLFを指定(LFの場合は"\n")
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
実行後の/tmp/test/sample.txt
あいうえお
かきくけこ
さしすせそ
たちつてと
なにぬねの
はひふへほ

文字コードAで読んで、文字コードBで書き出す

public class Main {
    public static void main() {
        // 読み込み対象(Windows-31J)
        Path sourceFile = Path.of("./w31j.txt");
        // 書き込み対象(EUC-JP)
        Path destFile = Path.of("./euc.txt")
        
        try (Stream<String> inStream = Files.lines(
                     sourceFile,
                     // 読み込む文字コードを指定
                     Charset.forName("Windows-31J"));
             BufferedWriter bufferedWriter = Files.newBufferedWriter(
                     destFile,
                     // 書き込む文字コードを指定
                     Charset.forName("EUC-JP"),
                     StandardOpenOption.CREATE,
                     StandardOpenOption.APPEND)
        ) {
            inStream.forEach(line -> {
                try {
                    bufferedWriter.write(line);
                    bufferedWriter.write("\n");
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Discussion