Java入門|ファイル入出力の基本

ファイルへ気をためるようにデータを残せると、Javaの入出力はぐっと実践的になる

これまで、画面に文字を表示したり、キーボードから文字列を受け取ったりする処理を見てきました。
こうしたやり取りはとても大切ですが、それだけではデータを長く残しておくことはできません。
せっかく入力した内容も、プログラムが終われば消えてしまいます。

そこで登場するのが、ファイルです。
ファイルを使えるようになると、データを保存したり、あとで読み返したり、大量の情報を管理したりできるようになります。
ここから、Javaのプログラムは一気に実用的になっていきます。

ドラゴンボールの世界でたとえるなら、その場で口頭で伝えた修行内容は時間がたつと消えてしまいますが、修行記録を巻物や端末に残しておけば、あとで悟空もベジータもピッコロも見返すことができますよね。
ファイルは、そうした修行記録を保存しておく場所のようなものです。

そして大事なのは、ファイルの読み書きも、これまで学んだストリームの考え方で扱えることです。
つまり、相手がキーボードからファイルに変わっても、データの流れとして見るという基本は変わりません。
ここでは、ファイル出力の基本を、ドラゴンボールの世界観に置き換えながら、順番にやさしく整理していきます。

ファイルを使う意味

キーボード入力や画面出力は、その場のやり取りには向いています。
でも、次のようなことをしたくなると、ファイルが必要になります。

やりたいことなぜファイルが必要か
データをあとで読み返したい画面表示だけでは残らないから
大量のデータを保存したいメモリだけでは保持し続けられないから
プログラム終了後も内容を残したいファイルなら外部に保存できるから
別のプログラムでも使いたいファイルなら共有しやすいから

ドラゴンボール風にいうと、その場の会話だけでは修行計画は流れて消えてしまいますが、神様の神殿の記録室に保存しておけば、あとから確認できます。
ファイルは、その記録室のような存在です。

ファイル入出力もストリームで考える

ファイルを使うと聞くと、まったく新しい難しい話のように感じるかもしれません。
でもJavaでは、ファイルの読み書きもストリームの考え方で扱います。

つまり、

  • データが流れていく
  • 文字として扱う
  • 効率よく読み書きする
  • 1行単位で扱う

といった基本は、これまでの入出力と大きく変わりません。

キーボード入力では、System.in から始めて InputStreamReader や BufferedReader を組み合わせました。
今回のファイル出力では、FileWriter、BufferedWriter、PrintWriter を組み合わせます。

このように、相手が変わっても「ストリームを組み立てて扱う」という感覚は共通しています。

ファイルへの出力とは何か

ファイルに対する出力とは、プログラムの中のデータをファイルへ書き出すことです。
たとえば、文字列を1行ずつ保存していくような処理です。

ドラゴンボールの世界でいえば、修行の結果をその場で言うだけではなく、修行記録ファイルに書き込んで保管するようなイメージです。
記録しておけば、あとで見返したり、別の日の修行と比較したりできます。

サンプルプログラムで見てみよう

ここでは、ファイルへ文字列を2行書き出すシンプルな例を見ていきます。
内容は、修行記録を書き込む形です。

ファイル名:Sample7.java

import java.io.*;

class Sample7
{
    public static void main(String[] args)
    {
        try
        {
            PrintWriter pw = new PrintWriter(
                new BufferedWriter(
                    new FileWriter("training.txt")
                )
            );

            pw.println("悟空:重力修行を開始した");
            pw.println("ベジータ:気の集中を続けている");
            System.out.println("修行記録をファイルに保存しました。");

            pw.close();
        }
        catch(IOException e)
        {
            System.out.println("ファイルの入出力で問題が発生しました。");
        }
    }
}

このプログラムの役割

このプログラムは、training.txt というファイルに文字列を2行書き出します。
そのあと、画面に保存完了のメッセージを表示しています。

つまり流れとしては、次のようになります。

手順内容
1ファイルに書き込むためのストリームを作る
2println で1行ずつ文字列を書き込む
3保存したことを画面に表示する
4close でファイルを閉じる

実行後、training.txt の中には次のような内容が保存されます。

悟空:重力修行を開始した
ベジータ:気の集中を続けている

FileWriter の役割

最初に登場するのが FileWriter です。

new FileWriter("training.txt")

これは、指定したファイルへ文字を書き込むための文字ストリームを作っています。
ここでは training.txt という名前のファイルを対象にしています。

役割を表で整理するとこうなります。

クラス名役割
FileWriterファイルへ文字を書き込むための基本の文字ストリーム

ドラゴンボール風にたとえるなら、記録を保存するための巻物や記録端末を開く役目です。
まず保存先を決めないと、どこにも記録を書き込めません。

BufferedWriter の役割

次に、それを BufferedWriter で包んでいます。

new BufferedWriter(new FileWriter("training.txt"))

BufferedWriter は、バッファを使って効率よく書き込むためのクラスです。
細かい書き込みをそのつど直接行うのではなく、いったんためながら処理することで効率がよくなります。

クラス名役割
BufferedWriterバッファを使って効率よく書き込む

ドラゴンボールの世界でいうと、修行記録を1文字ずつ神殿の記録庫へ運ぶのではなく、いったん手元の記録板にまとめてから送るようなものです。
こうすることで、処理がスムーズになります。

PrintWriter の役割

さらにその外側を PrintWriter で包んでいます。

new PrintWriter(
    new BufferedWriter(
        new FileWriter("training.txt")
    )
)

PrintWriter は、println のようなメソッドを使って、1行ずつ書き出しやすくするためのクラスです。

クラス名役割
PrintWriter1行単位で文字を出力しやすくする

今回のサンプルで使っている

pw.println("悟空:重力修行を開始した");
pw.println("ベジータ:気の集中を続けている");

が書きやすいのは、この PrintWriter のおかげです。

ドラゴンボール風にたとえるなら、修行記録を「1項目ずつ整った形で記録できる係」がいるようなものです。
そのため、コードが読みやすく、扱いやすくなります。

なぜクラスを重ねて使うのか

このコードを見て、最初は
「どうしてこんなに何重にもクラスを重ねるのだろう」
と感じるかもしれません。

でも、それぞれ役割が違います。

組み合わせ何をしているか
FileWriter保存先のファイルへ文字を書けるようにする
BufferedWriter効率よく書き込めるようにする
PrintWriterprintln で1行ずつ扱いやすくする

つまり、1つのクラスですべてを担当するのではなく、役割を分けて積み重ねているわけです。

これはドラゴンボールの戦いで、
悟空が攻撃、ピッコロが援護、ブルマが装置管理を担当するように、
それぞれの得意分野を組み合わせて全体を作っているイメージに近いです。

println で1行ずつ書き込む

ファイルへの書き込みそのものは、次の行で行っています。

pw.println("悟空:重力修行を開始した");
pw.println("ベジータ:気の集中を続けている");

println は、文字列を書き込んだあとで改行も入れてくれます。
そのため、1行ずつ自然にファイルへ保存できます。

今回のプログラムでは、2回 println を呼び出しているので、ファイルには2行の内容が残ります。

close が必要な理由

ファイルを扱うときに、とても大切なのが close です。

pw.close();

これは、ファイルを閉じる処理です。

ファイルは開いて書き込むだけでは終わりではありません。
最後にきちんと閉じることで、書き込みを完了させ、資源を解放します。
これを忘れると、内容が正しく保存されなかったり、ファイルが使いっぱなしになったりすることがあります。

メソッド役割
closeファイルを閉じて、書き込みを完了させる

ドラゴンボール風にいうと、修行記録を書いたあとに記録庫の扉を閉めて保管を完了するようなものです。
書くだけ書いて扉を開けっぱなしにしていたら、きちんと管理できませんよね。
ファイルも同じです。

なぜ try-catch が必要なのか

このプログラムでは、ファイル処理全体を try-catch で囲んでいます。

catch(IOException e)
{
    System.out.println("ファイルの入出力で問題が発生しました。");
}

これは、ファイルの読み書きでは IOException が起こる可能性があるからです。
たとえば、保存先に問題があったり、書き込み中に何か不具合が起きたりする可能性があります。

つまり、ファイルはプログラム内部の変数だけを扱うのとは違い、外部の資源とやり取りしています。
そのため、例外処理が必要になるわけです。

ドラゴンボールでたとえるなら、頭の中で修行計画を考えるだけなら安全ですが、外部の記録装置や通信装置を使うと、装置側の事情でうまくいかないこともある、という感じです。

ファイル出力の流れを図で整理する

この図は、Javaプログラムの文字データが、PrintWriter、BufferedWriter、FileWriter を通ってファイルへ書き込まれる流れを表しています。
それぞれのクラスが別の役割を担当していて、組み合わせることで使いやすく効率のよいファイル出力が実現されていることが分かります。

この図から読み取れることは、次の通りです。

分かること内容
PrintWriter の役割1行ずつ書き込みやすくする
BufferedWriter の役割効率よく出力する
FileWriter の役割実際のファイルへ文字を書き出す
close の意味最後に保存を完了させるために必要

ファイル出力と画面出力の共通点

ここでぜひ意識したいのは、ファイル出力も画面出力も、どちらも「外へデータを出す」という点では同じだということです。
違うのは出力先だけです。

出力先使うもの
画面System.out
ファイルFileWriter などのストリーム

つまり、画面への出力をもっと発展させて、「保存先がファイルになった」と考えると理解しやすいです。
ストリームの考え方が共通しているからこそ、ここがつながって見えてきます。

ファイルを使えるようになると何ができるか

ファイル出力を理解すると、プログラムでできることが一気に広がります。

できること
ログを残す修行の実行記録を保存する
設定を保存する修行レベルや重力設定を残す
データをあとで使う次回起動時に前回の内容を読む
大量データを扱う多数の戦士情報を保存する

その場で表示するだけのプログラムから、一歩進んで「記録を残せるプログラム」になるわけです。
これは実用的なプログラムにとって、とても大きな違いです。

ここで押さえておきたいポイント

最後に、このテーマで大事な点を整理しておきます。

ポイント内容
ファイルの役割データを長く保存するための場所
FileWriterファイルへ文字を書き込む基本のストリーム
BufferedWriter効率よく書き込むためのストリーム
PrintWriterprintln で1行ずつ書き出しやすくする
println文字列を1行単位で書き込む
closeファイルを閉じて保存を完了する
IOExceptionファイル入出力時の問題に備える例外

ファイル入出力は、最初はクラスがいくつも重なって見えて少し複雑に感じるかもしれません。
でも、それぞれの役割を分けて理解すると、とても筋の通った仕組みだと分かってきます。

ドラゴンボールの世界でも、修行結果をその場で言うだけではすぐに流れてしまいますが、記録庫に残しておけば次の修行につなげられます。
Javaのファイル出力もまさに同じで、データをその場限りで終わらせず、未来へ残すための大切なしくみです。