Files でファイルに書き込む

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • Files.writeString(path, text) で文字列をファイルに一発書き込み (Java 11+)
  • Files.write(path, lines)List<String> を行単位で書き込む
  • デフォルトは UTF-8、上書きモード。追記したいときは StandardOpenOption.APPEND
  • 書き込み中の例外は IOException (検査例外)。try-catchthrows で扱う

Files でファイルに書き込む とは

Java の標準ライブラリ java.nio.file.Files を使って、ファイルへの書き込みと読み込みをわずか数行で実現する方法を学ぼう。

ファイル I/O を最短コードで書ける java.nio.file.Files

アプリを作っていると、設定値を保存したい、ログを残したい、ユーザーの作成したテキストを保存したい、といった「ファイルにデータを書く」場面が必ず出てきます。Java でファイル操作を扱うクラスは歴史的にいくつもありますが、現代の Java で まず最初に覚えるべきは java.nio.file.Files です。Java 7 で導入され、Java 11 で writeString / readString が追加されたことで、ファイルへの読み書きが文字どおり 1 行で書けるようになりました。

古い書籍やネット記事には FileWriterBufferedReader をネストして使う例がよく載っていますが、2020 年代の Java では Files.writeString / Files.readString を第一候補に据えてください。try-with-resourcesclose() を自分で書かなくて済むぶん、バグの入る余地が劇的に減ります。

本章では、一時ファイルにテキストを書き、それを読み返して返す、というシンプルな課題を通して、Files クラスと Path の使い方、そして古い API との違いを身につけていきましょう。Android アプリでも、サーバーサイドのバッチでも、ファイル I/O は避けて通れない基礎なので、ここでしっかり押さえます。

Path でファイルの場所を表す

Files クラスを使う前に、まず「どのファイルを操作するか」を表す型 java.nio.file.Path を知る必要があります。PathWindowsC:\Users\foo.txtmacOS Linux/tmp/foo.txt のような ファイルパスの抽象表現 で、OS の違いを吸収してくれます。

Java

import java.nio.file.Path; import java.nio.file.Paths; Path p1 = Path.of("/tmp/hello.txt"); // Java 11+ Path p2 = Paths.get("/tmp/hello.txt"); // Java 7+ の書き方 Path p3 = Path.of("data", "users", "a.json"); // OS 依存の区切り文字で結合

Path.of(...) は Java 11 以降で使える比較的新しい書き方で、内部的には Paths.get(...) と同じです。複数の文字列を渡すと、Windows では \ で、Linux では / で勝手に結合してくれるので、パス区切り文字をハードコードする必要がありません。

ファイルパスを単なる String で持ち回ると、区切り文字やエンコーディングの違いでバグが起きがちです。Path を使えば、API 側がそういった OS 差を吸収してくれるので、移植性の高いコードになります。

Files.writeString で 1 行書き込み

本題の書き込み API は Files.writeString(Path, CharSequence) です。これ 1 行で、内部で自動的にファイルを開き、書き込み、閉じるまでをやってくれます。try-with-resources ですら書かなくて済みます。

Java

import java.nio.file.Files; import java.nio.file.Path; public class Demo { public static void main(String[] args) throws Exception { Path p = Path.of("hello.txt"); Files.writeString(p, "Hello, Java!"); String text = Files.readString(p); System.out.println(text); // Hello, Java! } }

ここでのポイントは次のとおりです。

  • Files.writeString は既存ファイルがあれば 中身を上書き する (追記モードではない)
  • 文字コードは Java 11 での追加当初から常に UTF-8 固定 (JEP 400 以前も同様)
  • メソッドは IOException を投げるので、throws Exceptiontry-catch が必須
  • 戻り値は書き込んだ Path で、メソッドチェーンに使うこともできる

書き込みと対になる読み込み API が Files.readString(Path) です。これも 1 行で「ファイルの中身全部を String で返す」というシンプルな仕様で、BufferedReader で 1 行ずつループする必要はありません。

diagram (will load when visible)

一時ファイルを安全に作る Files.createTempFile

本番のコードで「特定のパスを決め打ち」すると、テストや並列実行で衝突を起こします。そこで便利なのが Files.createTempFile(prefix, suffix) です。OS が管理する一時ディレクトリ (Windows なら %TEMP%Linux macOS なら /tmp など) に、一意なファイル名でファイルを作ってくれます。

Java

import java.nio.file.Files; import java.nio.file.Path; Path tmp = Files.createTempFile("demo", ".txt"); System.out.println(tmp); // 例: /tmp/demo12345.txt Files.writeString(tmp, "一時データ"); String read = Files.readString(tmp); Files.delete(tmp); // 後片付け

第一引数の "demo" がファイル名の 接頭辞、第二引数の ".txt"拡張子 で、間に乱数文字列が入ります。後片付けに Files.delete(path) を呼ぶか、JVM 終了時に消したい場合は tmp.toFile().deleteOnExit() を使うのが定石です。

chotdekiru の Java executor は一時ファイルの作成を許可しています。本課題でも Files.createTempFile を遠慮なく使ってください。逆に、決め打ちのパス (/etc/passwdC:\\Windows\\System32 など) を触ろうとするとセキュリティ的にブロックされる可能性があります。

古い API (FileWriter / BufferedReader) との比較

なぜ昔のサンプルで FileWriterBufferedReader が登場していたかというと、Java 6 以前にはまだ Files クラスがなく、これしか手段がなかったからです。試しに同じ処理を 2 つの API で書き比べてみます。

Java

// 古い書き方 (Java 6 以前のスタイル) import java.io.*; FileWriter fw = null; try { fw = new FileWriter("old.txt"); fw.write("hello"); } finally { if (fw != null) fw.close(); }

Java

// 新しい書き方 (Java 11+) import java.nio.file.Files; import java.nio.file.Path; Files.writeString(Path.of("new.txt"), "hello");

古い書き方では close()finally で呼ばないとファイルハンドルが残ってしまう、文字コードを指定し忘れるとプラットフォーム依存になる、改行コードのことを意識する必要がある、と地雷が多めです。新しい書き方ではこれらがほぼ全部吸収されているので、特別な理由がなければ Files 一択です。

diagram (will load when visible)

よくある間違い

ファイル I/O は地味にハマりやすい領域です。先回りして 3 つの落とし穴を押さえておきましょう。

  • close() を呼び忘れる / 古い書き方をするnew FileWriter(...) 系の古い API を使うと、finallyclose() を書かないとリソースが残ります。try-with-resources (try (var w = new FileWriter(...)) { ... }) を必ず使うか、いっそ Files.writeString に置き換えるのが正解です
  • エンコーディングを指定し忘れる — 古い new FileWriter("a.txt") は OS のデフォルトエンコーディングで書き込むので、Windows (Shift_JIS) と Linux (UTF-8) で結果が変わることがあります。Files.writeString は常に UTF-8 なので安全です
  • IOException を握りつぶす — ファイル操作は失敗する可能性が常にあります (権限・容量・パスの存在)。catch (IOException e) {} のように空ブロックで握りつぶすと、トラブルが起きても原因が分からなくなるので、必ず e.printStackTrace()throw new RuntimeException(e) で見える形にしましょう

もう一つよくあるのが、Files.writeFiles.writeString を混同するパターンです。Files.write(Path, byte[]) はバイト列、Files.writeString(Path, String) は文字列、と引数が違うので、用途に応じて使い分けます。テキストデータなら writeString でほぼ事足ります。

Files.write(Path, Iterable<? extends CharSequence>) という、行のリストを渡してファイルに書き込むオーバーロードもあります。List<String> をそのまま改行区切りで書き出したいときに便利です。

実務で遭遇するパターン

中級レッスンで学んだことが、実際の業務コードでどう登場するかを整理します。

  • ログ出力 ─ ライブラリではなく独自フォーマットでログを残したいとき、Files.writeString + APPEND で素朴に書く
  • バッチの中間ファイル ─ 集計結果を CSV や TSV にダンプして次の工程へ渡すパターン
  • 設定ファイルの書き出し ─ ユーザー設定を ~/.appname/config.json に保存する、CLI ツール定番
  • サーバーレスのレポート生成Lambda/tmp に一時ファイルを書いて S3 にアップする流れ

コードレビューで指摘されがちなポイント

PR を出すとシニアから入りやすい指摘です。先回りで身につけておけばレビューが一発で通ります。

  • 文字コード未指定 ─ Java 8 までの Files.write(path, lines) はプラットフォーム依存。StandardCharsets.UTF_8 を明示するよう指摘
  • 大量データを String 連結で組み立てている+= のループは遅い。StringBuilderBufferedWriter を使う
  • 追記したいのに上書きで開いているStandardOpenOption.APPEND 忘れで既存内容を消すバグの定番
  • try-with-resources を使っていないBufferedWriter を手動 close するコードは漏れの温床。try-with-resources 必須

パフォーマンス考慮事項

  • Files.writeString は内部で一度フルバッファ ─ 数 MB 以上は BufferedWriter でストリーミング書き込みのほうが省メモリ
  • fsync の有無 ─ デフォルトはクラッシュ時に直前の書き込みが失われる可能性あり。重要データは FileChannel.force(true) を使う
  • OS のページキャッシュ ─ 連続書き込みはディスク IO ではなくキャッシュへのコピーが多い。flush() だけでは永続化されない
  • 並列書き込みは禁物 ─ 同じファイルに複数スレッドで書くと内容が壊れる。synchronizedFileLock で排他制御
この章のポイント

ここまでの要点 Files.writeString で 1 行書き込み、追記は APPEND、文字コードは UTF_8 を明示。大量書き込みは BufferedWriter + try-with-resources

やってみよう

それでは課題に挑戦してみましょう。Solution.writeAndRead(String content) を実装し、与えられた content を一時ファイルに書き、それを読み返して返します。手順は次のとおりです。

  1. メソッドは throws Exception か、内部で try-catch する形でシグネチャを合わせる
  2. java.nio.file.Files.createTempFile("demo", ".txt") で一時ファイルを作る
  3. Files.writeString(path, content) で書き込む
  4. Files.readString(path) で読み返して、戻り値として return する
  5. 後片付けとして Files.delete(path) を呼ぶ (テストには直接影響しないが、行儀がよい)

テストでは、空文字列 ""、英字 "hello"、日本語 "日本語" の 3 ケースで「書いた内容と読んだ内容が一致するか」を比較します。Files.writeString / Files.readString は UTF-8 がデフォルトなので、特に文字コードを指定しなくても日本語が正しく往復します。

慣れてきたら、Files.createTempDirectory で一時ディレクトリを作ったり、Files.write(path, List.of("line1", "line2")) で複数行を一気に書いたり、Files.lines(path)Stream<String> として読んだりと、いろいろ寄り道してみてください。Files クラスのメソッド一覧を眺めるだけでも、Java の標準ライブラリの厚みを実感できるはずです。

よくある質問

Q. 中級の内容は実務でどれくらい使いますか?

A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。

Q. Stream と for ループはどっちで書くべき?

A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。

Q. 次のステップでは何を学ぶべきですか?

A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。

次のレッスン

次は ファイルを行単位で読む で、Java の標準ライブラリ `java を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. Files.write の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. Files.write とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. java.nio.file.Files.createTempFile を使って一時ファイルを作成すること (パスのハードコードは禁止)
  2. Files.writeString(path, content) で書き込み、Files.readString(path) で読み返した値を return すること
  3. throws Exceptiontry-catchIOException を適切に扱うこと

入出力例

test-cases.txt

writeAndRead("hello")"hello" writeAndRead("")"" writeAndRead("日本語")"日本語" writeAndRead("line1 line2") → "line1 line2"

ヒント

main.java
main.java
学習モード

メモ

Files でファイルに書き込む

⌘S で保存