Javaでファイルを読み書きするなら、java.nio.file.Files クラスの static メソッドを使うのが基本です。 Files.readString(path) で全部読み、Files.writeString(path, text) で書く、のように1行で書けます。Java 8世代では BufferedReader と FileReader を組み合わせて丁寧に書いていました。その書き方は今も動き、Files はそれを短く書ける道具です。

この記事では、「やりたいこと→Filesのメソッド」の逆引き表を最初に置き、読み書きから一覧・CSVまで、すべてJava 21(OpenJDK 21.0.10、Linux)の単体ファイル実行(java X.java)で動かした実行結果つきで紹介します。Shift_JIS/MS932の文字化け、Files.lines を閉じないときに残るファイル数、約149MBのファイルを読むときのメモリも実測しました。

Java ファイル操作の逆引き早見表

やりたいこと 使うメソッド 補足
全部を文字列で読む Files.readString(path) Java 11以降。既定はUTF-8
行のリストで読む Files.readAllLines(path) 改行は含まない。全行をメモリに載せる
1行ずつ読む(大きいファイル) Files.lines(path) Stream。try-with-resourcesで閉じる
1行ずつ読む(ループで) Files.newBufferedReader(path) readLine() を null まで
文字列を書く(上書き) Files.writeString(path, text) Java 11以降。なければ作成
行のリストを書く Files.write(path, lines) 各行の後ろに改行
追記する Files.writeString(path, text, StandardOpenOption.APPEND) ファイルがないと例外(CREATE と併用で作成)
新規作成のときだけ書く StandardOpenOption.CREATE_NEW 既にあれば FileAlreadyExistsException
文字コードを指定 各メソッドの第2引数に Charset Charset.forName("MS932") など
存在確認 Files.exists(path)、Files.notExists(path) フォルダか:Files.isDirectory
フォルダ作成 Files.createDirectories(path) 途中もまとめて作る。既にあってもOK
コピー Files.copy(src, dst, REPLACE_EXISTING) オプションなしで既存なら例外
移動・名前変更 Files.move(src, dst, REPLACE_EXISTING)
削除 Files.delete(path)、Files.deleteIfExists(path) 中身のあるフォルダは消せない
直下の一覧 Files.list(dir) Stream。閉じる
サブフォルダも含めて一覧 Files.walk(dir) Stream。閉じる
条件で探す Files.find(dir, 深さ, 条件) 属性(サイズ等)で絞れる
サイズ Files.size(path) バイト数
更新日時 Files.getLastModifiedTime(path) 変更は setLastModifiedTime
一時ファイル Files.createTempFile("pre-", ".csv") OSの一時フォルダ
2つのファイルの比較 Files.mismatch(a, b) Java 12以降。同じなら -1

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java ファイル読み込み・書き込み|Filesの逆引き表と実行結果」

ファイルを読み込む・書き込む・追記する

小さめのテキストファイルなら、readString/writeString の2つでほとんど足ります。 追記や「新規のときだけ」は StandardOpenOption で指定します。

import java.io.IOException;
import java.nio.file.*;
import java.util.List;

public class ReadWrite {
    public static void main(String[] args) throws IOException {
        Path dir = Files.createDirectories(Path.of("work"));
        Path memo = dir.resolve("memo.txt");

        // 書く(なければ作成、あれば上書き)。文字コードの既定は UTF-8
        Files.writeString(memo, "1行目\n2行目\n");
        // 追記する
        Files.writeString(memo, "3行目\n", StandardOpenOption.APPEND);
        // 行のリストを書く(各行の後ろに改行が付く)
        Files.write(dir.resolve("items.txt"), List.of("ペン", "ノート", "付箋"));

        // 全部を1つの文字列で読む
        String all = Files.readString(memo);
        System.out.print(all);
        // 行のリストで読む(改行は含まない)
        List<String> lines = Files.readAllLines(dir.resolve("items.txt"));
        System.out.println(lines + " 件数=" + lines.size());

        // 新規作成のみ許可(既にあれば例外)
        try {
            Files.writeString(memo, "x", StandardOpenOption.CREATE_NEW);
        } catch (FileAlreadyExistsException e) {
            System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
        }
    }
}

実行結果:

1行目
2行目
3行目
[ペン, ノート, 付箋] 件数=3
FileAlreadyExistsException: work/memo.txt
StandardOpenOption 意味
指定なし CREATE+TRUNCATE_EXISTING+WRITE(なければ作成、あれば中身を空にして書く)
APPEND 末尾に追記
CREATE なければ作る(APPEND と組み合わせて「なければ作って追記」)
CREATE_NEW 新規のときだけ作る。あれば FileAlreadyExistsException

writeString は文字列の \n をそのまま書き、Files.write(path, lines) と newLine() はOSの改行(Windowsは \r\n)を付けます。

1行ずつ読む・書く(大きいファイル向け)

ログのように大きくなりうるファイルは、全部を読み込まずに1行ずつ処理します。 Files.lines はStreamで行を順に流すので、filter などと組み合わせられます。

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.*;
import java.util.stream.Stream;

public class LineByLine {
    public static void main(String[] args) throws IOException {
        Path log = Path.of("work", "access.log");
        Files.createDirectories(log.getParent());

        // 1行ずつ書く:BufferedWriter(try-with-resources で必ず閉じる)
        try (BufferedWriter w = Files.newBufferedWriter(log)) {
            for (int i = 1; i <= 5; i++) {
                w.write("2026-09-28 10:0" + i + " " + (i % 2 == 0 ? "ERROR" : "INFO") + " task-" + i);
                w.newLine();
            }
        }

        // 1行ずつ読む:Files.lines は Stream。閉じる必要がある
        try (Stream<String> lines = Files.lines(log)) {
            lines.filter(l -> l.contains("ERROR")).forEach(System.out::println);
        }
    }
}

実行結果:

2026-09-28 10:02 ERROR task-2
2026-09-28 10:04 ERROR task-4

try-with-resourcesが要るメソッド・要らないメソッド

Files.lines・list・walk・find が返すStreamは、ファイルを開いたままにしています。 Javadocも try-with-resources での使用を求めています。閉じないとどうなるか、Linuxの /proc/self/fd(開いているファイルの一覧)で数えました。

import java.io.IOException;
import java.nio.file.*;
import java.util.stream.Stream;

public class LeakCheck {
    // Linux の /proc/self/fd で、このプロセスが開いているファイル数を数える
    static long openFiles() throws IOException {
        try (Stream<Path> s = Files.list(Path.of("/proc/self/fd"))) { return s.count(); }
    }

    public static void main(String[] args) throws IOException {
        Path file = Files.writeString(Path.of("work", "small.txt"), "a\nb\nc\n");
        long before = openFiles();

        for (int i = 0; i < 100; i++) {
            long n = Files.lines(file).count();          // 閉じていない
        }
        System.out.println("閉じない場合 : +" + (openFiles() - before));

        long mid = openFiles();
        for (int i = 0; i < 100; i++) {
            try (Stream<String> s = Files.lines(file)) { // try-with-resources で閉じる
                long n = s.count();
            }
        }
        System.out.println("閉じた場合   : +" + (openFiles() - mid));
    }
}

実行結果:

閉じない場合 : +100
閉じた場合   : +0

閉じないと、呼んだ回数だけファイルが開いたまま残りました。 長時間動くサーバーでは、開けるファイル数の上限に達して Too many open files になることがあります。

メソッド 閉じる必要 理由
readString、readAllLines、readAllBytes 不要 読み終えたらメソッド内で閉じる
writeString、write 不要 書き終えたらメソッド内で閉じる
lines、list、walk、find 必要 Streamがファイル・フォルダを開いたまま
newBufferedReader、newBufferedWriter 必要 Reader/Writerを返す
newInputStream、newOutputStream、newDirectoryStream 必要 ストリームを返す

文字コードを指定する(Shift_JIS・MS932の文字化けを再現)

Files のメソッドは、文字コードを指定しなければUTF-8として読み書きします。 Windowsのツールが出力したCSVなどを読むときは、文字コードを指定します。

import java.io.IOException;
import java.nio.charset.*;
import java.nio.file.*;

public class CharsetDemo {
    public static void main(String[] args) throws IOException {
        Charset ms932 = Charset.forName("MS932");     // Windows の「Shift_JIS」
        Charset sjis = Charset.forName("Shift_JIS");
        Path file = Path.of("work", "sjis.csv");
        Files.createDirectories(file.getParent());

        // MS932 で書き出す(Windowsのツールが出力したCSVを想定)
        Files.writeString(file, "品名,数量\n①ノート~A4,3\n", ms932);
        System.out.println("バイト数: " + Files.size(file));

        // (1) 文字コードを指定せずに読む → 既定の UTF-8 として解釈して例外
        try {
            Files.readString(file);
        } catch (MalformedInputException e) {
            System.out.println("(1) " + e.getClass().getName() + ": " + e.getMessage());
        }
        // (2) バイトを UTF-8 の文字列にする → 例外にならず文字化け
        System.out.println("(2) " + new String(Files.readAllBytes(file), StandardCharsets.UTF_8).lines().toList());
        // (3) Shift_JIS で読む → ① など Windows 拡張の文字で例外
        try {
            Files.readString(file, sjis);
        } catch (MalformedInputException e) {
            System.out.println("(3) Shift_JIS: " + e.getMessage());
        }
        // (4) MS932 で読む → 正しく読める
        System.out.println("(4) " + Files.readString(file, ms932).lines().toList());
    }
}

実行結果:

バイト数: 25
(1) java.nio.charset.MalformedInputException: Input length = 1
(2) [�i��,����, �@�m�[�g�`A4,3]
(3) Shift_JIS: Input length = 1
(4) [品名,数量, ①ノート~A4,3]
読み方 結果 理由
(1) readString(file) MalformedInputException UTF-8として読めないバイトがあると例外で止まる
(2) new String(bytes, UTF_8) 文字化け(�) 読めないバイトを置換文字にしてそのまま進む
(3) readString(file, Shift_JIS) MalformedInputException ① はWindowsの拡張文字で、Shift_JIS には含まれない
(4) readString(file, MS932) 正しく読める Windowsの「Shift_JIS」は実際には MS932(別名 windows-31j)

Windowsで作られた日本語ファイルは MS932 を指定すると、①・~・髙 などの文字も読めます。Files.readString と readAllLines が化けずに例外で知らせてくれるのは、気づかないまま化けたデータを保存しない点で親切な動きです。

存在確認・フォルダ作成・コピー・移動・削除

フォルダ作成は createDirectories なら途中もまとめて作れ、既にあってもエラーになりません。 上書きのコピー・移動は REPLACE_EXISTING を付けます。

import java.io.IOException;
import java.nio.file.*;

public class DirOps {
    public static void main(String[] args) throws IOException {
        Path base = Path.of("work", "ops");
        Path sub = base.resolve("2026/09");

        Files.createDirectories(sub);                   // 途中のフォルダもまとめて作る
        Path src = Files.writeString(sub.resolve("report.txt"), "月次レポート");
        System.out.println("exists      : " + Files.exists(src));
        System.out.println("isDirectory : " + Files.isDirectory(sub));
        System.out.println("notExists   : " + Files.notExists(base.resolve("none.txt")));

        // コピー(既にあれば上書き)
        Path copy = Files.copy(src, sub.resolve("report_copy.txt"), StandardCopyOption.REPLACE_EXISTING);
        // 移動・名前変更
        Path moved = Files.move(copy, base.resolve("report_backup.txt"), StandardCopyOption.REPLACE_EXISTING);
        System.out.println("moved       : " + moved + " / " + Files.readString(moved));

        // 削除
        Files.delete(moved);
        System.out.println("deleteIfExists: " + Files.deleteIfExists(moved)); // もう無いので false

        // よくある例外を実際に出す
        try {
            Files.readString(base.resolve("none.txt"));
        } catch (NoSuchFileException e) {
            System.out.println("NoSuchFileException         : " + e.getMessage());
        }
        try {
            Files.delete(base);                          // 中身があるフォルダ
        } catch (DirectoryNotEmptyException e) {
            System.out.println("DirectoryNotEmptyException  : " + e.getMessage());
        }
        try {
            Files.copy(src, Files.writeString(sub.resolve("old.txt"), "旧版")); // コピー先が既にある
        } catch (FileAlreadyExistsException e) {
            System.out.println("FileAlreadyExistsException  : " + e.getMessage());
        }
    }
}

実行結果:

exists      : true
isDirectory : true
notExists   : true
moved       : work/ops/report_backup.txt / 月次レポート
deleteIfExists: false
NoSuchFileException         : work/ops/none.txt
DirectoryNotEmptyException  : work/ops
FileAlreadyExistsException  : work/ops/2026/09/old.txt
例外 起きる場面 getMessage() の中身
NoSuchFileException 読む・消す・コピー元がない、親フォルダがない 対象のパス
FileAlreadyExistsException copy・move・CREATE_NEW で同名がある 対象のパス
DirectoryNotEmptyException 中身のあるフォルダを delete 対象のパス
MalformedInputException 文字コードが合わない Input length = 1 など

どれも IOException のサブクラスで、メッセージはパスだけです。 ログには例外クラス名と一緒に出します。

フォルダの中身を一覧・検索する、サイズと更新日時を取る

直下だけなら list、サブフォルダも含めるなら walk、サイズなどの条件で探すなら find を使います。

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.stream.Stream;

public class ListFiles {
    public static void main(String[] args) throws IOException {
        Path root = Path.of("work", "tree");
        Files.createDirectories(root.resolve("docs/old"));
        Files.writeString(root.resolve("a.txt"), "A");
        Files.writeString(root.resolve("b.csv"), "id,name\n1,pen\n");
        Files.writeString(root.resolve("docs/c.txt"), "CCC");
        Files.writeString(root.resolve("docs/old/d.txt"), "DDDD");

        // 直下だけ(サブフォルダの中は見ない)
        try (Stream<Path> s = Files.list(root)) {
            System.out.println("list : " + s.map(p -> p.getFileName().toString()).sorted().toList());
        }
        // サブフォルダも含めてすべて(深さ制限も可能)
        try (Stream<Path> s = Files.walk(root)) {
            System.out.println("walk : " + s.filter(Files::isRegularFile).map(root::relativize).sorted().toList());
        }
        // 条件で探す(属性も見られる)
        try (Stream<Path> s = Files.find(root, Integer.MAX_VALUE,
                (p, attr) -> attr.isRegularFile() && p.toString().endsWith(".txt") && attr.size() >= 3)) {
            System.out.println("find : " + s.map(root::relativize).sorted().toList());
        }
        // ワイルドカード(glob)で直下を絞る
        try (DirectoryStream<Path> ds = Files.newDirectoryStream(root, "*.{txt,csv}")) {
            for (Path p : ds) System.out.println("glob : " + p.getFileName());
        }

        // サイズ・更新日時
        Path b = root.resolve("b.csv");
        System.out.println("size : " + Files.size(b) + " bytes");
        Files.setLastModifiedTime(b, FileTime.from(Instant.parse("2026-09-01T00:00:00Z")));
        System.out.println("modified : " + Files.getLastModifiedTime(b));
    }
}

実行結果:

list : [a.txt, b.csv, docs]
walk : [a.txt, b.csv, docs/c.txt, docs/old/d.txt]
find : [docs/c.txt, docs/old/d.txt]
glob : b.csv
glob : a.txt
size : 14 bytes
modified : 2026-09-01T00:00:00Z

list・walk の並び順は保証されないので sorted() を付けています。 newDirectoryStream の順番(b.csv → a.txt)も、ファイルシステムによって変わります。フォルダを中身ごと消すときは、walk の結果を逆順(深い方から)にして1つずつ delete します。

一時ファイルとCSVの簡易読み込み

テストや中間データには createTempFile が便利です。 使い終わったら finally で消します。CSVは、カンマを含む値や改行を含む値がない単純な形式なら、split で読めます。

import java.io.IOException;
import java.nio.file.*;
import java.util.List;
import java.util.stream.Stream;

public class TempAndCsv {
    record Item(int id, String name, int stock) {}

    public static void main(String[] args) throws IOException {
        // 一時ファイル(OSの一時フォルダに一意の名前で作られる)
        Path tmp = Files.createTempFile("items-", ".csv");
        try {
            Files.writeString(tmp, """
                    id,name,stock
                    1,ボールペン,120
                    2,ノート,45

                    3,付箋,0
                    """);

            // 簡易CSV読み込み:ヘッダーを飛ばし、空行を除き、record に変換
            List<Item> items;
            try (Stream<String> lines = Files.lines(tmp)) {
                items = lines.skip(1)
                        .filter(l -> !l.isBlank())
                        .map(l -> l.split(",", -1))
                        .map(c -> new Item(Integer.parseInt(c[0]), c[1], Integer.parseInt(c[2])))
                        .toList();
            }
            items.forEach(System.out::println);
            System.out.println("在庫0: " + items.stream().filter(i -> i.stock() == 0).map(Item::name).toList());
        } finally {
            Files.deleteIfExists(tmp);  // 使い終わったら消す
        }
        System.out.println("削除済み: " + Files.notExists(tmp));
    }
}

実行結果:

Item[id=1, name=ボールペン, stock=120]
Item[id=2, name=ノート, stock=45]
Item[id=3, name=付箋, stock=0]
在庫0: [付箋]
削除済み: true

split(",", -1) の -1 は、末尾の空の列を捨てないための指定です。 値にカンマや改行を含む引用符つきCSVは、CSV用ライブラリを使うのが安心です。

大きいファイルを読むときのメモリ(readAllLinesとlinesを実測)

readAllLines は全行を List<String> としてメモリに載せ、lines は1行ずつ流します。 200万行・約149MBのログファイルで、最大ヒープ(-Xmx)を変えて比べました。

import java.io.*;
import java.nio.file.*;
import java.util.List;
import java.util.stream.Stream;

public class BigFile {
    public static void main(String[] args) throws IOException {
        Path big = Path.of("work", "big.log");
        if (Files.notExists(big)) {
            Files.createDirectories(big.getParent());
            try (BufferedWriter w = Files.newBufferedWriter(big)) {
                for (int i = 0; i < 2_000_000; i++) {
                    w.write("2026-09-28T10:00:00 INFO order-" + i + " status=RECEIVED item=notebook qty=" + (i % 9));
                    w.newLine();
                }
            }
        }
        System.out.printf("ファイル: %,d bytes / 最大ヒープ: %,d MB%n",
                Files.size(big), Runtime.getRuntime().maxMemory() / 1024 / 1024);

        long start = System.nanoTime();
        long count;
        if (args[0].equals("readAllLines")) {
            List<String> all = Files.readAllLines(big);       // 全行をメモリに載せる
            count = all.stream().filter(l -> l.endsWith("qty=0")).count();
        } else {
            try (Stream<String> lines = Files.lines(big)) {    // 1行ずつ流す
                count = lines.filter(l -> l.endsWith("qty=0")).count();
            }
        }
        Runtime rt = Runtime.getRuntime();
        System.out.printf("%s: 件数=%,d 時間=%d ms 使用ヒープ(終了時点)=%,d MB%n", args[0], count,
                (System.nanoTime() - start) / 1_000_000, (rt.totalMemory() - rt.freeMemory()) / 1024 / 1024);
    }
}
java -Xmx128m BigFile.java readAllLines
java -Xmx128m BigFile.java lines
java -Xmx1g BigFile.java readAllLines
java -Xmx1g BigFile.java lines

実行結果(ファイル作成は1回目に実行。時間は環境によって変わります):

ファイル: 148,888,890 bytes / 最大ヒープ: 128 MB
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
ファイル: 148,888,890 bytes / 最大ヒープ: 128 MB
lines: 件数=222,223 時間=534 ms 使用ヒープ(終了時点)=19 MB
ファイル: 148,888,890 bytes / 最大ヒープ: 1,024 MB
readAllLines: 件数=222,223 時間=1213 ms 使用ヒープ(終了時点)=263 MB
ファイル: 148,888,890 bytes / 最大ヒープ: 1,024 MB
lines: 件数=222,223 時間=867 ms 使用ヒープ(終了時点)=46 MB
読み方 -Xmx128m -Xmx1g のときの使用ヒープ
readAllLines OutOfMemoryError: Java heap space 263MB(ファイルサイズより大きい)
lines 完了(19MB) 46MB

readAllLines はファイルサイズ以上のヒープを使いました。 目安として、数MBまでの設定ファイルやテンプレートは readString/readAllLines、ログやデータのように大きくなりうるものは lines と使い分けると安心です。

java.io.Fileとの対応表

java.io.File を使ったコードも、file.toPath() で Path に変換すれば Files のメソッドと組み合わせられます。 既存のコードを少しずつ置き換えるときに便利です。

やりたいこと java.io.File java.nio.file.Files / Path
パスを作る new File("dir", "a.txt") Path.of("dir", "a.txt")
相互変換 file.toPath() path.toFile()
存在確認 file.exists() Files.exists(path)
フォルダ作成 file.mkdirs()(boolean) Files.createDirectories(path)
削除 file.delete()(boolean) Files.delete(path)(失敗は例外)
一覧 file.listFiles() Files.list(path)
サイズ file.length() Files.size(path)

大きな違いは失敗の伝え方です。 File は false を返し、Files は理由のわかる例外を投げます。

よくある質問

Q. Java 8では readString や writeString は使えますか? A. Java 11で追加されたため、Java 8では new String(Files.readAllBytes(path), StandardCharsets.UTF_8) と Files.write(path, text.getBytes(StandardCharsets.UTF_8)) で同じことができます。 readAllLines・lines・newBufferedReader などはJava 8から使えます。

Q. 書き込み中に異常終了してファイルが半端になるのを避けるには? A. 一時ファイルに書いてから Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE) で置き換える方法があります。 同じファイルシステム上なら、読む側は古いファイルか新しいファイルのどちらかだけを見ることになります。

まとめ

  • 読み書きの基本は Files.readString/writeString(Java 11以降)。追記は StandardOpenOption.APPEND
  • 文字コードの既定はUTF-8。Windows作成の日本語ファイルは MS932 を指定すると ① なども読める
  • lines・list・walk・find は try-with-resources で閉じる。閉じないと開いたファイルが残る(実測で100回→100個)
  • フォルダ作成は createDirectories、上書きのコピー・移動は REPLACE_EXISTING
  • 例外のメッセージはパスだけなので、例外クラス名と一緒にログへ出す
  • 大きいファイルは lines。約149MBで readAllLines は128MBヒープで OutOfMemoryError、lines は完了
  • java.io.File は toPath() で Files と組み合わせられる

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java ファイル読み込み・書き込み|Filesの逆引き表と実行結果」

株式会社RJCでは、ファイル操作のような身近な処理も、実際に動かして確かめながら仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?