Java道|ストリームによるファイル入力

保存した修行記録を読み出せると、Javaは過去のデータを今の処理に活かせる。
ファイル入力を理解すると、プログラムはその場限りの処理から、記録をもとに判断できる実践的な処理へ進みます。

これまで、Javaでは画面へ文字を表示したり、キーボードから文字列を受け取ったり、ファイルへデータを書き込んだりする方法を学んできました。

ファイルへ出力できるようになると、プログラムの中で作ったデータを残せるようになります。

たとえば、修行記録、実行ログ、設定値、隊士の情報などをファイルに保存しておけば、プログラムが終了したあとでも内容を残せます。

ただし、ファイルに保存するだけでは、まだ片方向です。

保存したデータをあとから読み込めるようになると、Javaプログラムはさらに実用的になります。

鬼滅の刃風にたとえると、鬼殺隊本部の記録室に修行記録の巻物を残しておくだけでなく、必要なときにその巻物を開いて、過去の修行内容や隊士の戦闘記録を確認できるようになるイメージです。

Javaの処理鬼滅の刃風のイメージ
ファイルへ出力する修行記録を巻物に書き残す
ファイルから入力する巻物から過去の記録を読み出す
readLineで読む巻物を1行ずつ読む
数値へ変換する記録された文字を戦闘力の数値として扱う
配列へ入れる複数の隊士記録を名簿へ整理する

ファイル入力では、保存されているデータをプログラムの中へ取り込みます。

このときも、キーボード入力やファイル出力と同じように、ストリーム の考え方を使います。

ストリームとは、データの流れです。

ファイル入力では、データがファイルからJavaプログラムへ向かって流れてきます。

この記事では、まず Sample8.java でファイルから2行の文字列を読み込みます。
次に Sample9.java でファイルから複数の数値を読み込み、配列に入れて、最大値と最小値を求める流れを整理します。

ファイルから入力するとは何か

ファイルから入力するとは、保存してあるデータをプログラムの中へ取り込むことです。

画面表示だけのプログラムでは、結果をその場で見ることはできます。
キーボード入力では、ユーザーがその場で文字を入力できます。

しかし、大量のデータを毎回キーボードから入力するのは大変です。

たとえば、8人分、100人分、1000人分の修行記録を毎回手入力するのは現実的ではありません。

そこで、データをあらかじめファイルに保存しておき、プログラムがそれを読み込むようにします。

方法特徴
キーボードから毎回入力する少量の入力には向いているが、大量データには大変
ファイルから読み込む保存済みのデータをまとめて扱いやすい
コードに直接書く変更のたびにソースコード修正が必要
ファイルに分けるデータ変更だけならファイルを直せばよい

鬼滅の刃風に言えば、修行のたびに「前回の記録を口頭で教えてください」と聞き直すより、本部の記録室から巻物を取り出して確認するほうが確実です。

ファイル入力は、この「巻物から記録を読み出す」処理です。

ファイル入力でもストリームを使う

ファイルからの読み込みも、ストリームで考えます。

ファイル入力では、ファイルからプログラムへデータが流れます。

その流れを作るために、基本的には次のクラスを使います。

クラス役割
FileReaderファイルから文字を読み込むための基本の文字ストリーム
BufferedReaderバッファを使って効率よく読み込み、readLineを使いやすくする

FileReader は、どのファイルを読むのかを指定します。

BufferedReader は、その読み込みを効率よくし、さらに readLine で1行ずつ読み取れるようにしてくれます。

鬼滅の刃風にたとえると、FileReader は記録室から目的の巻物を開く係です。

BufferedReader は、巻物の内容を一気に扱いやすい形へ整え、1行ずつ読み上げられるようにする記録係です。

Java鬼滅の刃風のイメージ
training1.txt修行記録の巻物
FileReader巻物を開く係
BufferedReader読みやすく整理する記録係
readLine巻物を1行読む動作
String読み取った1行の文字列

図:ファイル入力の基本の流れ

↓クリックすると拡大表示されます。

この図が示していること

この図では、ファイルに保存されている文字列が、Javaプログラムの中へ読み込まれる流れを表しています。

training1.txt は、保存済みの修行記録です。

FileReader がファイルを読み込むための入口を作ります。
BufferedReader が効率よく読み込める形に整えます。
readLine によって、1行分の文字列が String として取り出されます。

図の要素意味
training1.txt読み込み対象のファイル
FileReaderファイルから文字を読み込む基本の流れ
BufferedReader効率よく読み込み、1行ずつ扱いやすくする
readLine1行分を文字列として読み込む
System.out読み込んだ内容を画面に表示する

この図から分かることは、ファイル入力もストリームによるデータの流れとして扱えるということです。

まずは文字列を2行読み込む

最初に、ファイルから2行の文字列を読み込むシンプルな例を見ます。

同じフォルダに、次のようなファイルを用意しておきます。

ファイル名:training1.txt

水月の朝修行
蒼真の夜修行

この training1.txt を読み込むプログラムが Sample8.java です。

ファイル名:Sample8.java

import java.io.*;

class Sample8
{
    public static void main(String[] args)
    {
        try{
            BufferedReader br =
                new BufferedReader(new FileReader("training1.txt"));

            String str1 = br.readLine();
            String str2 = br.readLine();

            System.out.println("修行記録ファイルに書かれている2つの内容は");
            System.out.println(str1 + "です。");
            System.out.println(str2 + "です。");

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

Sample8.java の処理の流れ

このプログラムでは、training1.txt から2行の文字列を読み込み、画面に表示しています。

処理の流れは次の通りです。

手順内容
1FileReader で training1.txt を読み込む準備をする
2BufferedReader で包んで読み込みやすくする
3readLine を2回使って2行取り出す
4取り出した文字列を画面に表示する
5close でファイルを閉じる
6問題が起きたら IOException を catch する

ここで大切なのは、キーボード入力でも使った readLine が、ファイル入力でも使われていることです。

入力元がキーボードからファイルに変わっても、1行ずつ文字列を取り出す考え方は同じです。

FileReader の役割

Sample8.java では、次の部分で FileReader を使っています。

new FileReader("training1.txt")

FileReader は、指定したファイルから文字を読み込むための基本のストリームです。

ここでは、training1.txt を読み込み対象にしています。

部分意味
FileReaderファイルから文字を読み込むためのクラス
training1.txt読み込み対象のファイル名
new FileReader("training1.txt")training1.txt を読む準備をする

鬼滅の刃風に言えば、FileReader は本部記録室から training1.txt という巻物を開く係です。

どの巻物を読むのかをここで指定しているわけです。

BufferedReader の役割

次に、FileReader を BufferedReader で包んでいます。

BufferedReader br =
    new BufferedReader(new FileReader("training1.txt"));

BufferedReader は、バッファを使って効率よく読み込むためのクラスです。

さらに、readLine を使って1行ずつ読み込めるようにしてくれます。

クラス・メソッド役割
BufferedReader効率よく文字を読み込む
readLine1行分を文字列として読み取る

鬼滅の刃風にたとえると、巻物に書かれた記録をそのまま読むのではなく、記録係が読みやすい形に整え、1行ずつ確認していくようなものです。

readLine で1行ずつ読み込む

読み込みそのものは、次の2行で行っています。

String str1 = br.readLine();
String str2 = br.readLine();

1回目の readLine で1行目が読み込まれます。
2回目の readLine で2行目が読み込まれます。

training1.txt の内容が次のようになっている場合、

水月の朝修行
蒼真の夜修行

変数には次のように入ります。

コード読み込まれる内容
String str1 = br.readLine();水月の朝修行
String str2 = br.readLine();蒼真の夜修行

そのあと、読み込んだ文字列を画面へ表示しています。

System.out.println("修行記録ファイルに書かれている2つの内容は");
System.out.println(str1 + "です。");
System.out.println(str2 + "です。");

実行結果

Sample8.java を実行すると、次のような結果になります。

修行記録ファイルに書かれている2つの内容は
水月の朝修行です。
蒼真の夜修行です。

この実行結果から、training1.txt に保存されていた2行の内容が、プログラムに読み込まれ、画面へ表示されていることが分かります。

鬼滅の刃風に言えば、本部記録室から巻物を開き、そこに書かれていた2つの修行記録を読み上げている状態です。

ファイルから大量のデータを読み込む意味

ファイル入力の便利さは、2行や3行の文字列を読むことだけではありません。

本当に便利なのは、大量のデータをまとめて読み込めるところです。

たとえば、8人分の修行記録、100人分の成績、数百行のログ、複数日の記録などをファイルから読み込めます。

キーボードで大量の値を毎回入力するのは大変です。

しかし、あらかじめファイルに保存しておけば、プログラムがその内容を順番に読み込んで処理できます。

データの扱い方特徴
キーボードから毎回入力少量ならよいが、大量データには不向き
コードに直接書くデータ変更のたびにコード修正が必要
ファイルから読み込むデータを外部で管理でき、変更しやすい

鬼滅の刃風に言えば、複数の隊士の修行結果を本部の巻物に保存しておき、あとからまとめて読み出して、最高記録や最低記録を調べるようなものです。

数値を大量に読み込む例

次は、ファイルから数値データを読み込み、最大値と最小値を求める例を見ていきます。

同じフォルダに、次のようなファイルを用意しておきます。

ファイル名:training2.txt

80
68
22
33
56
78
33
56

ここでは、8人分の修行戦闘力メモとして考えます。

このデータを Sample9.java で読み込みます。

ファイル名:Sample9.java

import java.io.*;

class Sample9
{
    public static void main(String[] args)
    {
        try{
            BufferedReader br =
                new BufferedReader(new FileReader("training2.txt"));

            int[] power = new int[8];
            String str;

            for(int i = 0; i < power.length; i++){
                str = br.readLine();
                power[i] = Integer.parseInt(str);
            }

            int max = power[0];
            int min = power[0];

            for(int i = 0; i < power.length; i++){
                if(max < power[i])
                    max = power[i];
                if(min > power[i])
                    min = power[i];

                System.out.println(power[i]);
            }

            System.out.println("最高の戦闘力は" + max + "です。");
            System.out.println("最低の戦闘力は" + min + "です。");

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

Sample9.java の前半で行っている処理

前半では、training2.txt から1行ずつ読み込み、その文字列を int に変換して配列に入れています。

int[] power = new int[8];
String str;

for(int i = 0; i < power.length; i++){
    str = br.readLine();
    power[i] = Integer.parseInt(str);
}

ここで大切なのは、readLine で読み込まれるデータは 文字列 だということです。

training2.txt の中には 80 や 68 のように数字が書かれています。

しかし、ファイルから readLine で読み込んだ直後は、数値ではなく String として扱われます。

そのため、計算に使うには Integer.parseInt で int に変換する必要があります。

処理役割
br.readLine()1行を文字列として読む
Integer.parseInt(str)文字列を int に変換する
power[i] = ...変換した数値を配列へ入れる

鬼滅の刃風に言えば、巻物から読み出した戦闘力は、最初は文字として記録されています。

それを実際に比較や計算に使うために、数値の戦闘力として解釈し直しているイメージです。

図:文字列を数値に変換して配列へ入れる流れ

↓クリックすると拡大表示されます。

この図が示していること

この図では、training2.txt の数値らしきデータが、プログラム内で本当の int 型の数値として扱えるようになるまでの流れを表しています。

ファイルの中では 80 のように見えていても、readLine で読み込むと文字列です。

そのため、Integer.parseInt を使って int に変換し、power 配列へ入れています。

図の要素意味
training2.txt数値データが保存されたファイル
br.readLine()1行を文字列として読み込む
String str読み込まれた文字列
Integer.parseInt(str)文字列を整数へ変換する
int[] power変換後の整数を保存する配列

この図から分かることは、ファイル入力では「読む」だけでなく、必要に応じて型を変換してから処理に使う必要があるということです。

for文で8行分を読み込む

Sample9.java では、for文を使って8行分のデータを読み込んでいます。

for(int i = 0; i < power.length; i++){
    str = br.readLine();
    power[i] = Integer.parseInt(str);
}

power.length は配列の長さです。

今回、power は次のように作られています。

int[] power = new int[8];

そのため、power.length は 8 です。

for文では、i が 0 から 7 まで変化します。

i読み込む行保存先
01行目power[0]
12行目power[1]
23行目power[2]
34行目power[3]
45行目power[4]
56行目power[5]
67行目power[6]
78行目power[7]

このように、ファイルの各行を順番に読み込み、配列の各要素へ格納しています。

鬼滅の刃風に言えば、巻物に書かれた8人分の戦闘力を、隊士名簿の power[0] から power[7] へ順番に写している状態です。

Sample9.java の後半で行っている処理

後半では、配列に入った8個の値を調べながら、最大値と最小値を求めています。

int max = power[0];
int min = power[0];

for(int i = 0; i < power.length; i++){
    if(max < power[i])
        max = power[i];
    if(min > power[i])
        min = power[i];

    System.out.println(power[i]);
}

最初に、max と min に power[0] を入れています。

int max = power[0];
int min = power[0];

これは、最初の値を基準にして、あとから他の値と比較していくためです。

変数初期値意味
maxpower[0]現時点の最高値
minpower[0]現時点の最低値

そのあと、for文で power[0] から power[7] までを順番に見ていきます。

もし現在の max より大きい値が見つかれば、max を更新します。

if(max < power[i])
    max = power[i];

もし現在の min より小さい値が見つかれば、min を更新します。

if(min > power[i])
    min = power[i];

この処理によって、配列全体の最高値と最低値が分かります。

実行結果

training2.txt の内容が次のようになっているとします。

80
68
22
33
56
78
33
56

Sample9.java を実行すると、次のような結果になります。

80
68
22
33
56
78
33
56
最高の戦闘力は80です。
最低の戦闘力は22です。

最初に、読み込んだ8個の数値が順番に表示されます。

そのあと、最大値と最小値が表示されます。

項目結果
最高の戦闘力80
最低の戦闘力22

鬼滅の刃風に言えば、8人分の修行戦闘力を読み込み、その中で最も高い記録と最も低い記録を確認している状態です。

ファイル入力の便利さ

Sample9.java で大切なのは、数値データをJavaコードに直接書いていないことです。

データは training2.txt に保存されています。

そのため、戦闘力データを変更したい場合、Javaのソースコードではなく、training2.txt の中身を変更すればよいです。

方法データ変更時の作業
コードに直接数値を書くソースコードを修正して再コンパイルする必要がある
ファイルから読み込むファイルの中身を変更すればよい

これは、とても大きな利点です。

プログラムの処理の流れは変えずに、入力データだけを差し替えられるからです。

鬼滅の刃風に言えば、修行分析の仕組みは同じまま、巻物に書かれた隊士記録だけを入れ替えれば、別の日の記録や別の隊士たちの結果も分析できます。

キーボード入力との共通点

ファイル入力は、キーボード入力とよく似ています。

違うのは、入力元です。

項目キーボード入力ファイル入力
基本の考え方ストリームストリーム
1行読み込みreadLinereadLine
効率よく読むBufferedReaderBufferedReader
例外処理IOExceptionIOException
入力元System.inFileReader

キーボード入力では、System.in を InputStreamReader で包み、それを BufferedReader で扱いました。

ファイル入力では、FileReader を BufferedReader で包みます。

入力元使用する組み合わせ
キーボードInputStreamReader + BufferedReader
ファイルFileReader + BufferedReader

このつながりが見えると、Javaの入出力はばらばらの知識ではなく、ストリームという1つの考え方で整理できます。

close が必要な理由

ファイルを読み終わったら、close でファイルを閉じます。

Sample8.java でも Sample9.java でも、最後に close を呼び出しています。

br.close();

close は、使い終わったファイルを閉じるための処理です。

ファイルを開いたままにしておくと、資源を使い続けることになります。

メソッド役割
close読み込みが終わったファイルを閉じる

鬼滅の刃風に言えば、記録室から巻物を取り出して確認したあと、巻物を閉じて元の棚へ戻すようなものです。

読み終わった巻物を机の上に出しっぱなしにしないように、Javaでも読み込みが終わったら close で閉じることが大切です。

例外処理が必要な理由

Sample8.java と Sample9.java では、どちらも try-catch を使っています。

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

ファイルは外部資源です。

そのため、次のような問題が起きる可能性があります。

起こりうる問題内容
ファイルが存在しない指定したファイル名が見つからない
ファイルを読み込めない権限や状態に問題がある
読み込み中に問題が起きる外部資源とのやりとりで失敗する
ファイル名が間違っているtraining1.txt や training2.txt を正しく指定していない

このような入出力の問題に備えるために、IOException を catch しています。

鬼滅の刃風に言えば、記録室の巻物を開こうとしても、巻物が見つからない、棚が封印されている、記録が破損している、という可能性があります。

そのような場合に備えて、支援隊士が警告を受け止めるのが catch(IOException e) です。

図:大量データを読み込んで最大値・最小値を求める流れ

↓クリックすると拡大表示されます。

この図が示していること

この図では、training2.txt に保存された複数の数値を読み込み、配列に保存し、最大値と最小値を求める流れを表しています。

ファイルから読み込んだ直後のデータは文字列です。

それを Integer.parseInt で整数へ変換し、power 配列へ入れます。

その後、配列の中身を順番に比較して、max と min を更新します。

図の要素意味
training2.txt複数の数値データを保存したファイル
readLine1行ずつ文字列として読み込む
parseInt文字列を整数へ変換する
int[] power数値を保存する配列
max / min最大値と最小値

この図から分かることは、ファイル入力は単に文字を読むだけでなく、その後の計算や分析につなげられるということです。

ファイル入力で大切なポイント

ファイル入力を学ぶときは、次の点を押さえておくと理解しやすくなります。

ポイント内容
ファイル入力保存済みのデータをプログラムへ取り込むこと
FileReaderファイルを文字として読み込む基本のストリーム
BufferedReader効率よく読み込み、readLineを使いやすくする
readLine1行ずつ文字列として読み込む
Integer.parseInt文字列を整数へ変換する
配列読み込んだ複数の値をまとめて保存する
close読み込み後にファイルを閉じる
IOExceptionファイル入力時の問題に備える例外
ファイル入力の利点大量データをまとめて扱える

ファイル入力ができるようになると、Javaプログラムはかなり実用的になります。

その場で入力された値だけではなく、保存しておいたデータを読み込み、計算や分析に使えるようになるからです。

鬼滅の刃風に言えば、本部の記録室に残された修行記録を開き、隊士たちの成長や戦闘力を確認し、次の修行計画へ活かせるようになるということです。

ファイルから読み込む、文字列として受け取る、必要なら数値に変換する、配列へ入れて処理する。

この流れが分かると、ファイル入力は単なる読み込みではなく、データ活用の入口として理解できるようになります。