入出力ストリーム

Java基礎 12

1.本章での学習項目

本章では、ファイルからデータを読み込んだり、ファイルに書き込んだりする入出力ストリームに関して学習します。

2.本章の講義

本章で学習する内容を動画としてまとめたものです。最初に一通り見終わった後で、学習に入るようにしてください。

講義:入出力ストリーム

3.入出力ストリームとは

この章では、ファイルからのデータ読み込みや、ファイルへのデータを書き込み、コンソールに入力されたデータの読み込みなど、Javaの環境であるJVMから見ると、外部要素となるファイルやEclipseのコンソールとのデータのやりとりについて学習します。

入出力ストリームとは、JVMとファイルデータやEclipseのコンソールに入力されたデータとをやりとりするために用いられるオブジェクトです。
入出力ストリームは、そのオブジェクトでデータをやりとりできるようにデータ形式を変換しています。


iostream

3.1.ストリームの種類

入出力ストリームを分類すると、文字データを扱うための「文字ストリーム」と画像や音声などのデータを扱うための「バイトストリーム」があり、それぞれ別のクラスが用意されています。

3.1.1.文字ストリーム

文字ストリームとは、その名のとおり文字のデータをやりとりするストリームです。

文字ストリームの代表的なクラスは以下のとおりです。使い方は後述しますので、確認だけしておいてください。

入力(Reader)
BufferedReader 入力データをバッファリングして、文字ストリームから効率的にデータを読みます
FileReader ファイルから文字データを簡易的に読み込みます
InputStreamReader 指定されたエンコーディングによって、バイトデータを読んで文字に変換します
出力(Writer)
BufferedWriter 出力データをバッファリングして、文字ストリームに効率的にデータを出力します
OutputStreamWriter 指定されたエンコーディングによって、バイトデータを文字に変換して出力します
FileWriter ファイルに文字データを簡易的に出力します

文字ストリームはバイトストリームよりも入出力が効率的ですので、通常は文字ストリームを利用します。

3.1.2.バイトストリーム

画像のデータなどを扱うときは、バイト形式でデータのやりとりをします。これをバイトストリームと呼びます。

以下がバイトストリームのクラスです。

入力(InputStream)
DataInputStream バイトストリームから基本データ型のデータを読みます
FileInputStream ファイルからバイトデータを読みます
出力(OutputStream)
DataOutputStream バイトストリームから基本データ型のデータを出力します
FileOutputStream ファイルからバイトデータを出力します

3.2.入出力ストリームの処理概要

入出力ストリームの処理手順は、基本的に決まっています。各処理の実際のプログラムの記述については後で説明しますので、まずは処理の流れをざっくりと見ておきましょう。

3.2.1.入力の処理手順

入力の処理手順の基本は、以下のとおりです。

  1. ストリームを開く
    まず最初に、入力用のストリームのオブジェクトを生成します。
  2. データを読み込む
    次にストリームからデータを読み込みます。通常は1行ずつ読み込みします。
  3. ストリームを閉じる
    最後にストリームを閉じます。
3.2.2.出力の処理手順

出力の処理手順の基本は、以下のとおりです。入力と同じような手順です。

  1. ストリームを開く
    まず最初に、出力用のストリームのオブジェクトを生成します。
  2. データを書き込む
    次にストリームからデータを書き込みます。
  3. ストリームを閉じる
    最後にストリームを閉じます。
3.2.3.入出力パッケージのインポート

データの入出力を行うには、まず、入出力パッケージ「java.io」をインポートする必要があります。
このパッケージには、ファイルやコンソールとのデータの入出力を行うためのクラスが含まれています。

3.2.4.入出力の例外処理

入出力の処理は、例外処理が必須です。例外処理をきちんと行わないとコンパイルが通りません。
入出力で使用される例外クラスとしてIOExceptionクラスを覚えておくと良いでしょう。

入出力ストリームの概要はつかめたでしょうか。それでは、各処理パターンのプログラムの記述について学習していきましょう。

4.文字ストリーム

まずは、文字ストリームの各処理について学習します。

4.1.データの入力

データの入力処理として、コンソールに入力されたデータの読み込み処理とファイルデータの読み込み処理について確認します。

4.1.1.コンソールの文字読み込み

Eclipseのコンソールに文字を入力して、データを読み込む処理の学習です。

以下のサンプルソースは、実行するとコンソールにデータを入力できます。入力して「Enter」キーを押下すると、入力した文字がコンソールに出力されます。
「!」を入力すると入力の待機状態が終了となります。

IOTest1.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class IOTest1 {

	public static void main(String[] args) {
		System.out.println(" 「!」+ Enterで終了");

		BufferedReader br = null;

		try {
			// 入力ストリームを生成します
			br = new BufferedReader(new InputStreamReader(System.in));

			String str = "";

			// ! をコンソールで入力するまで、入力待機状態を繰り返します
			while (!str.equals("!")) {

				System.out.println("この後に入力 >");

				// 入力した値を読み込み、str変数に代入しています
				str = br.readLine();

				// 入力した値をコンソールに表示します
				System.out.println("入力した値は「" + str + "」です。");
			}

		} catch (IOException e) {
			// 例外内容を表示します
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// ストリームをクローズします
					br.close();

				} catch (IOException e) {
					// 例外内容を表示します
					e.printStackTrace();
				}
			}
		}
	}
}

実行すると以下のようになります。以下の実行結果では、「33」を入力してEnterキーを押下し、その後に「aaa」を入力してEnterキーを押下しています。

図1 実行結果
「!」 + Enterで終了
この後に入力 >
33
入力した値は「33」です。
この後に入力 >
aaa
入力した値は「aaa」です。
この後に入力 >

それでは、各処理の解説を行っていきます。
まず、以下の文で文字の入力ストリームを生成しています。

BufferedReader br = null;

// 入力ストリームを生成します
br = new BufferedReader(new InputStreamReader(System.in));

「System.in」がコンソールからの入力となります。コンソールから入力された値をInputStreamReaderクラスで文字に変換し、そのストリームオブジェクトをバッファリングするために、BufferedReaderクラスのオブジェクトも生成しています。

次の一文が入力された文字の読み込み処理です。

// 入力した値を読み込み、str変数に代入しています
str = br.readLine();

ストリームから1行読み込むには、readLineメソッドを使います。ここでは、読み込んだデータをstrという変数に代入し、コンソールに出力しています。

そして以下の一文で入力ストリームを閉じています。

// ストリームをクローズします
br.close();

このクローズ処理を忘れると、不要なバッファリング情報を保持し続ける可能性があるため、忘れずにクローズするようにしましょう。
例外が発生してもしなくても、クローズ処理は必ず実施したいため、finally句の中で行うようにしています。

最後に、以下の一文でIOExceptionが発生した場合に、エラー内容をコンソールに表示しています。

// 例外内容を表示します
e.printStackTrace();

printStackTraceメソッドは、発生した例外のクラス名や例外の説明、エラーが発生した行番号など、どこでどのようなエラーが起きたかを詳しく出力してくれるメソッドです。
例外発生原因を特定するための情報として非常に役立ちますので、特に理由が無い限りは、catch句の中に決まりごとのように記述するようにしておくと良いでしょう。

コンソールからの入力処理の説明は以上です。

4.1.2.ファイルからの文字読み込み

次に、ファイルから文字データを読み込む処理について学習します。

ファイルとは、データを保存したものです。保存するデータの種類はいろいろあり、文字データや画像、音など、通常OSは多数の種類のファイルを扱っています。
音楽であればmp3ファイルや、AACファイル、画像であればGIFファイルや、JPEGファイルなどが存在します。
ほとんどの場合、アプリケーション経由でファイルを操作するため、ファイルの存在をあまり認識しないかもしれませんが、ファイル無しではデータの受け渡しはできません。

ファイルには大きく分けて「テキストファイル」と「バイナリファイル」が存在します。
「テキストファイル」は文字データのみで構成されたファイルです。通常「テキストエディタ」と呼ばれるアプリケーションによって読むことが可能です。
Windowsの標準「テキストエディタ」は「メモ帳」というアプリケーションです。

ここでは、テキストファイルに保存された文字データを読み込む処理について確認していきます。
テキストファイルを読むには、FileReaderクラスを使います。読み込んだデータは、BufferedReaderクラスによって、バッファと呼ばれる一時的に保存するメモリーに保存されます。
あとはバッファに保存されたデータを1行ずつ読み込んで処理を行っていきます。

「C:\temp(Cドライブのtempフォルダ)」にファイル「temp1.txt」を作成し、何か文字を書いて保存してみましょう。tempフォルダが無い場合、新たに作成してください。
以下のサンプルソースでは、C:\temp(Cドライブのtempフォルダ)にあるファイル「temp1.txt」からデータをバッファに読み込み、1行ずつコンソールに出力しています。

バッファから1行ずつ読み込むには、上記のサンプルソースでも使ったreadLineメソッドを使います。

IOTest2.java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class IOTest2 {

	public static void main(String[] args) {

		// 開始のメッセージを出力します
		System.out.println("ファイルtemp1.txtからバッファに読み込む");

		BufferedReader br = null;

		try {
			// ファイルを指定して読み込むストリームを生成します
			br = new BufferedReader(new FileReader("C:\\temp\\temp1.txt"));

			String str = null;

			// ファイルには複数行文字が記述されてる場合もあるので繰り返し処理
			while ((str = br.readLine()) != null) {
					// 読み込んだファイルの内容を一行ずつ表示します
					System.out.println(str);
			}

		} catch (IOException e) {
			// 例外内容を表示します
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// ストリームをクローズします
					br.close();

				} catch (IOException e) {
					// 例外内容を表示します
					e.printStackTrace();
				}
			}
		}
	}
}

コンソールからの入力とほとんど同じような記述です。

まずは、読み込むファイルを指定してストリームを生成します。

BufferedReader br = null;

// ファイルを指定して読み込むストリームを生成します
br = new BufferedReader(new FileReader("C:\\temp\\temp1.txt"));

FileReaderクラスを使って指定されたファイルのデータを読み込むストリームを生成しています。
「”C:\\temp\\temp1.txt”」はCドライブにあるtempフォルダのtemp1.txtというテキストファイルという意味です。

実際のデータ読み込み処理は、以下の一文です。
複数行書かれているファイルから1行ずつreadLineメソッドで読み込みながら、str変数に代入し、そのstr変数がnullになるまで(読み込むデータがなくなるまで)繰り返し処理しています。

// ファイルには複数行文字が記述されてる場合もあるので繰り返し処理
while ((str = br.readLine()) != null) {

ストリームのクローズは、コンソールからの入力と同じです。

ファイルからの文字データの読み込み処理の説明は以上です。

4.2.データの出力

次に、文字データのファイルへの書き込み処理を学習します。
ファイルへの書き込みにはFileWriterクラスとBufferedWriterクラスを使います。

以下がサンプルソースです。コンソールに文字列を入力し、入力されたデータをC:\temp\temp2.txtに書き込んでいます。
書き込み処理のときは、該当ファイルが存在しない場合、そのファイルを新規で作成します。

IOTest3.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

public class IOTest3 {

	public static void main(String[] args) {
		System.out.println("キーボードから入力した内容をtemp2.txtに書き込みます");

		BufferedReader br = null;
		BufferedWriter bw = null;

		try {
			// コンソールからの入力を取得
			br = new BufferedReader(new InputStreamReader(System.in));

			String str = "";
			System.out.println("この後に入力 > ");
			str = br.readLine();

			// 書き込みストリームを生成します。
			bw = new BufferedWriter(new FileWriter("C:\\temp\\temp2.txt"));
			// ファイルに書き込みします
			bw.write(str);

		} catch (IOException e) {
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// 読み込みストリームをクローズします
					br.close();

				} catch(IOException e) {
					e.printStackTrace();
				}
			}
			if (bw != null) {
				try {
					// 書き込みストリームをクローズします
					bw.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

実行すると以下のような文字がコンソールに出力されますので、「この後に入力>」の後に何か文字を入力して、「Enter」キーを押下すると、ファイルにその文字列が書き込まれます。

図2 コンソールの出力内容
キーボードから入力した内容をtemp2.txtに書き込みます
この後に入力 >
・・何か入力するとそれがファイルに記述されます

書き込みのストリーム生成は以下の記述となります。

BufferedWriter bw = null;

// 書き込みストリームを生成します。
bw = new BufferedWriter(new FileWriter("C:\\temp\\temp2.txt"));

ファイルへの書き込みは、writeメソッドを使用します。

// ファイルに書き込みします
bw.write(str);

ストリームのクローズは、入力処理のときと同様です。

// ストリームを閉じます
bw.close();

ただし、出力処理の場合、クローズ処理を忘れるとバッファリングされた値がフラッシュされず、ファイルに書き出されません。
そのため、writeメソッドで書き込みを行っても、closeメソッドの呼び出しを忘れるとファイルが作成されたのに真っ白、ということになりかねませんので注意しましょう。

4.2.1.ファイルへの追加書き込み

ファイルへの書き込み処理で使うFileWriterクラスは、標準が上書きとなっています。つまり2行以上の文字列を記述しようとすると、1行目が2行目の文字によって上書きされてしまいます。
通常は、複数行のデータを出力したい場合は、追加で書き込みをしますので、追加書き込み処理についても学習しておきましょう。
追加書き込みにするためには、new FileWriter(file名、true)というように、File Writerクラスのコンストラクタに「true」を第2引数として設定するだけです。

以下がサンプルソースです。先ほどの書き込みのサンプルに「true」を追加してみました。

IOTest4.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

public class IOTest4 {

	public static void main(String[] args) {
		System.out.println("キーボードから入力した内容をtemp3.txtに書き込む");
		System.out.println(" 「!」+ Enterで終了");

		BufferedReader br = null;
		BufferedWriter bw = null;

		try {
			br = new BufferedReader(new InputStreamReader(System.in));

			// FileWriter() の引数に true を記述することでファイルへの追加書き込みとなります
			bw = new BufferedWriter(new FileWriter("C:\\temp\\temp3.txt", true));

			String str = "";

			while (!str.equals("!")) {
				System.out.println("この後に入力 > ");

				str = br.readLine();

				// 書き込み
				bw.write(str);

				// 改行
				bw.newLine();
			}

		} catch (IOException e) {
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// 読み込みストリームをクローズします
					br.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bw != null) {
				try {
					// 書き込みストリームをクローズします
					bw.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

通常は、追加書き込みとなりますので、「true」をつけることを忘れないようにしましょう。

文字データの入出力処理は以上です。

5.try-with-resources文

ここまで、コンソールやファイルとのデータの入出力方法について学習してきました。
そして、それらの一連の処理の最後には、必ずfinally句にクローズ処理を記述し、リソースを解放する必要があると説明をしました。

しかし、毎回クローズ処理を記述することは冗長的であり、冗長的であるが故にクローズ処理のし忘れなどが起こりやすく、バグの温床になりやすいという問題点があります。

そのため、Java 7よりtry-with-resources文という構文が導入されました。

5.1.try-with-resources文とは

例外処理の章で既に学習済みの、try-catch-finally句の構文の一つで、クローズ処理を省略しても、リソースの解放を自動的に行ってくれる機能です。

「4.1.1 コンソールの文字読み込み」のサンプルソース「IOTest1.java」を、try-with-resources文を使って書き換えた例が、以下になります。

IOTest5.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class IOTest5 {

	public static void main(String[] args) {
		System.out.println(" 「!」+ Enterで終了");

		// tryのあとに()を付けて、その中でストリームを生成します
		try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {

			String str = "";

			// ! をコンソールで入力するまで、入力待機状態を繰り返します
			while (!str.equals("!")) {

				System.out.println("この後に入力 >");

				// 入力した値を読み込み、str変数に代入しています
				str = br.readLine();

				// 入力した値をコンソールに表示します
				System.out.println("入力した値は「" + str + "」です。");
			}

		} catch (IOException e) {
			// 例外内容を表示します
			e.printStackTrace();
		}
	}
}

tryのあとに()を付加して、その中にストリーム生成処理を記述します。
また、finally句がなくなっていることに着目してください。
これにより、finally句でクローズ処理を記述しなくても、リソースの解放が自動的に行われます。

また、Java 9より、以下のサンプルのような書き方も可能になりましたので、紹介します。

IOTest6.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class IOTest6 {

	public static void main(String[] args) {
		System.out.println(" 「!」+ Enterで終了");

		// 入力ストリームを生成します
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		// tryのあとの()にストリームオブジェクトの変数を記述します
		try (br) {

			String str = "";

			// ! をコンソールで入力するまで、入力待機状態を繰り返します
			while (!str.equals("!")) {

				System.out.println("この後に入力 >");

				// 入力した値を読み込み、str変数に代入しています
				str = br.readLine();

				// 入力した値をコンソールに表示します
				System.out.println("入力した値は「" + str + "」です。");
			}

		} catch (IOException e) {
			// 例外内容を表示します
			e.printStackTrace();
		}
	}
}

ストリーム生成処理をtryの前で行うことができ、そのストリームオブジェクトをtryのあとの()内に記述することができます。
このような記法も可能であることを押さえておきましょう。

なお、クローズ処理までの一連の流れをしっかりと理解してもらうために、この教材では以降もtry-with-resources文を使わない書き方で解説を行っていきます。

try-with-resources文の説明は以上です。

6.Pathインターフェース・Filesクラス

また、Java7以降ではFileクラスに代わって、PathインターフェースやFilesクラスを使うことが可能になりました。
これにより、ソースの記述を簡略化することができるようになりました。
例として、コピー処理を取り上げます。
Java6以前ではファイルをコピーして、あるディレクトリに保存する場合には自前で処理を実装しなければなりませんでした。

まずは、ここまでで学習した入出力ストリームを用いた書き方です。

IOTestCopy.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class IOTestCopy {

	public static void main(String[] args) {

		BufferedReader br = null;
		BufferedWriter bw = null;

		try {
			// ファイルを指定して読み込むストリームを生成します
			br = new BufferedReader(new FileReader("C:\\temp\\temp1.txt"));
			// 書き込みストリームを生成します
			bw = new BufferedWriter(new FileWriter("C:\\temp\\temp2.txt"));
			String str = null;

			// ファイルには複数行文字が記述されてる場合もあるので繰り返し処理
			while ((str = br.readLine()) != null) {
				// 読み込んだファイルの内容をファイルに書き込みします
				bw.write(str);
				bw.newLine();
			}

		} catch (IOException e) {
			// 例外内容を表示します
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// ストリームをクローズします
					br.close();

				} catch (IOException e) {
					// 例外内容を表示します
					e.printStackTrace();
				}
			}
			if (bw != null) {
				try {
					// ストリームをクローズします
					bw.close();

				} catch (IOException e) {
					// 例外内容を表示します
					e.printStackTrace();
				}
			}
		}
	}
}

それではPathインターフェースを用いた処理を確認しましょう。
変数”br”および”bw”への代入の仕方が先ほどの処理と異なることに注目しましょう。

IOTestCopy2.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class IOTestCopy2 {

	public static void main(String[] args) {

		BufferedReader br = null;
		BufferedWriter bw = null;
		Path inputPath = Paths.get("C:\\temp\\temp1.txt");
		Path outputPath = Paths.get("C:\\temp\\temp21.txt");

		try {
			// ファイルを指定して読み込むストリームを生成します
			br = Files.newBufferedReader(inputPath);
			// 書き込みストリームを生成します
			bw = Files.newBufferedWriter(outputPath);

			String str = null;

			// ファイルには複数行文字が記述されてる場合もあるので繰り返し処理
			while ((str = br.readLine()) != null) {
				// 読み込んだファイルの内容をファイルに書き込みします
				bw.write(str);
				bw.newLine();
			}

		} catch (IOException e) {
			// 例外内容を表示します
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// ストリームをクローズします
					br.close();

				} catch (IOException e) {
					// 例外内容を表示します
					e.printStackTrace();
				}
			}
			if (bw != null) {
				try {
					// ストリームをクローズします
					bw.close();

				} catch (IOException e) {
					// 例外内容を表示します
					e.printStackTrace();
				}
			}
		}
	}
}

最後にFilesクラスのコピーメソッドを用いたコピー処理を確認します。
さきほどの処理では入力ストリームで読み込んだファイルの中身がなくなるまで出力処理をしていましたが、
FilesクラスのCopyメソッドを用いることで記述量が格段に少なくなることに注目しましょう。

IOTestCopy3.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class IOTestCopy3 {

	public static void main(String[] args) {

		Path inputPath = Paths.get("C:/temp/temp1.txt");
		Path outputPath = Paths.get("C:/temp/temp2.txt");

		try {
			// iputPath変数の内容をoutPath変数のファイルへコピーします
			Files.copy(inputPath, outputPath, StandardCopyOption.REPLACE_EXISTING);
		} catch (IOException e) {
			// 例外内容を表示します
			e.printStackTrace();
		}
	}
}

PathインターフェースとFilesクラスを用いることで、ソースの記述内容が簡略化されたことを確認できたと思います。
PathインターフェースFilesクラスにはここで紹介した以外のメソッドがまだまだあります。
詳細はリンク先を参照してください。

Information

Pathインターフェースでは様々なパスの表現方法が存在します。
今までFileクラスで取得していたパスですが、Pathインターフェースを使用することにより、パスの記述の自由度が上がりました。
確認したい場合は下記のクラスを作成して、実際に動かしてみましょう。

PathTest.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathTest {

	public static void main(String[] args) {

		Path pathA = Paths.get("C:/temp/sample2.txt");
		Path pathB = Paths.get("C:/temp", "sample2.txt");
		Path pathC = Paths.get("C:", "temp", "sample2.txt");

		if (pathA.equals(pathB) && pathA.equals(pathC)) {
			System.out.println("pathAとpathBとpathCは同値です。");
		} else {
			System.out.println("pathAとpathBとpathCは同値ではありません。");
		}

		// 出力結果「pathAとpathBとpathCは同値です。」

	}
}

7.練習問題1

それでは、ファイル入出力の練習問題を行ってみましょう。

7.1.問題1

「C:\temp\mondaitemp1.txt」を作成し、以下の内容を入力して保存してください。

Javaの入出力処理は、ファイル操作を中心に考えると良いでしょう。

ファイル操作のプログラムは、ほとんど定型文です。
ファイルのパスの指定を適切に行えば、正しく処理が実行されるでしょう。

保存したファイルを読み込み、ファイルの内容を「C:\temp\mondaikekka1.txt」に書き込むプログラムを作成してください。
なお、ファイルを保存する際、文字コードは「UTF-8」で保存してください。
メモ帳であれば、保存するときに「文字コード」のプルダウンが下の方にありますので、そこで「UTF-8」を選べばOKです。

IOMondai1_1.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class IOMondai1_1 {

	public static void main(String[] args) {

		BufferedReader br = null;
		BufferedWriter bw = null;

		try {
			// mondaitemp1.txtファイルを読み込むストリームを生成
			br = new BufferedReader(new FileReader("C:\\temp\\mondaitemp1.txt"));

			// mondaikekka1.txtファイルを書き込むストリームを生成
			bw = new BufferedWriter(new FileWriter("C:\\temp\\mondaikekka1.txt", true));

			// 文字列格納用変数を宣言と初期化
			String str = null;

			// 1行ずつファイルデータを読み込み
			while ((str = br.readLine()) != null) {
				// 読み込んだデータをmondaikekka1.txtファイルに出力
				bw.write(str);
				// 改行
				bw.newLine();
			}

		} catch (IOException e) {
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// 読み込みストリームをクローズします
					br.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bw != null) {
				try {
					// 書き込みストリームをクローズします
					bw.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

ファイルパスの指定を間違えないように気をつけましょう。
また、書き込みのファイルをオープンするときに、「追加書き込み」のための「true」引数で受け渡すことを忘れないようにしてください。
書き込み処理は、繰り返し処理が終わった後では、読み込んだデータが残っていませんので、読み込みの繰り返しの処理の中に記述します。

8.練習問題2

コンソールからの入力とファイル出力の練習問題です。

8.1.問題1

以下の実行結果のように、「りんご」、「バナナ」、「なし」、「もも」を順番に入力します。
入力された値を4つの要素を持つ文字列型配列に格納し、格納した配列の要素をコンソールに出力するプログラムを作成してください。
なお、「!」を入力した場合はその時点で終了するようにしてください。このとき、「!」は配列に格納されないようにしてください。
また、文字列型配列は4つの要素しか入らないため、4回値を入力された場合も処理を終了するようにしてください。
※ ヒント:
1. コンソールへの入力受付を繰り返し(while文またはfor文)行う際、条件式に4回までしか繰り返さないように指定します。
2. 「!」が入力された場合に終了する処理は、繰り返し処理の中で、条件分岐で判定し、「!」が入力されたらbreakして、強制的に繰り返し処理を抜けるようにしましょう。

図5 実行結果
「!」 + Enterで終了
この後に入力 >
りんご
この後に入力 >
バナナ
この後に入力 >
なし
この後に入力 >
もも
りんご
バナナ
なし
もも

IOMondai2_1.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class IOMondai2_1 {

	public static void main(String[] args) {

		// メッセージ出力
		System.out.println("「!」 + Enterで終了");

		BufferedReader br = null;

		try {
			// 4つの要素を持つ文字列型配列の定義
			String[] fruit = new String[4];

			// 読み込んだ値を格納する変数定義
			String str = "";

			// 配列の添え字用変数定義
			int i = 0;

			// 入力ストリームの生成
			br = new BufferedReader(new InputStreamReader(System.in));

			// 文字列型配列の数まで、入力待機状態を繰り返し
			while (i < fruit.length) {
				// メッセージ出力
				System.out.println("この後に入力 >");

				// 入力された値を変数に格納
				str  = br.readLine();

				// 「!」が入力された場合、繰り返し処理を終了
				if(str.equals("!")) {
							break;
				}
				// 入力された値を配列に格納
				fruit[i] = str;
				i++;
			}

			// 配列の要素を取得しコンソール出力
			for (i = 0; i < fruit.length; i++) {
				if (fruit[i] != null) {
					// 出力
					System.out.println(fruit[i]);
				}
			}

		} catch (IOException e) {
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// 読み込みストリームをクローズします
					br.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

コンソールに「!」が入力されるまで、入力処理を続けるには、「str.equals(“!”)」で判定できます。
「!」が入力された場合、breakで繰り返し処理を抜けるようにしましょう。
一通りの処理が終了したら、最後にストリームをクローズすることを忘れないようにしましょう。

8.2.問題2

問題1で作成したプログラムを参考にして、「りんご」、「バナナ」、「なし」、「もも」を格納した順番とは逆の順番で配列から要素を取り出して、コンソールに出力するプログラムを作成してください。

※この問題の解答は掲載しておりません。Tech Fun ITスクールのJava研修では、講師が丁寧に解説しています。

8.3.問題3

問題1、問題2で作成したプログラムを参考にして、「りんご」、「バナナ」、「なし」、「もも」を格納した順番とは逆の順番で配列から要素を取り出して、「C:\temp\mondaikekka2.txt」に書き込むプログラムを作成してください。

IOMondai2_3.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.FileWriter;
import java.io.IOException;

public class IOMondai2_3 {

	public static void main(String[] args) {

		// メッセージ出力
		System.out.println("「!」 + Enterで終了");

		BufferedReader br = null;
		BufferedWriter bw = null;

		try {
			// 4つの要素を持つ文字列型配列の定義
			String[] fruit = new String[4];

			// 読み込んだ値を格納する変数定義
			String str = "";

			// 配列の添え字用変数定義
			int i = 0;

			// 入力ストリームの生成
			br = new BufferedReader(new InputStreamReader(System.in));

			// 文字列型配列の数まで、入力待機状態を繰り返し
			while (i < fruit.length) {
				// メッセージ出力
				System.out.println("この後に入力 >");

				// 入力された値を変数に格納
				str  = br.readLine();

				// 「!」が入力された場合、繰り返し処理を終了
				if (str.equals("!")) {
							break;
				}
				// 入力された値を配列に格納
				fruit[i] = str;
				i++;
			}

			// 出力ストリームの生成
			bw = new BufferedWriter(new FileWriter("C:\\temp\\mondaikekka2.txt", true));

			// 逆順に配列の要素を取得しファイルに出力
			for (i = fruit.length - 1; i >= 0; i--) {
				if (fruit[i] != null) {
					// 出力
					bw.write(fruit[i]);

					// 改行
					bw.newLine();
				}
			}

		} catch (IOException e) {
			e.printStackTrace();

		} finally {
			if (br != null) {
				try {
					// 読み込みストリームをクローズします
					br.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bw != null) {
				try {
					// 書き込みストリームをクローズします
					bw.close();

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

書き込みストリームのクローズも忘れないようにしましょう。

9.本章のまとめ

  • 入力系のストリームとして、ファイルを読み込む「FileReader」やコンソール入力を受け取る「InputStreamReader」などがある。
  • 出力系のストリームには、ファイル出力を行う「FileWriter」などがある。
  • 入力/出力ともに、処理の基本的な流れは「ストリームを開く→データの読み書きを行う→ストリームを閉じる」である。
  • ストリームは必ず閉じないといけないため、基本的にはtry-catch-finally文を使用して、finally句の中にストリームのクローズ処理を書かなければいけない。
  • 例外処理をきちんと実装しないとコンパイルエラーが発生する。
  • 自動的にストリームのクローズ処理を行う構文として「try-with-resources文」がある。
  • Fileクラスの代わりに、PathインターフェースやFilesクラスを使うことも出来る。

入出力に関する実装はある程度決まった形になっているので、まずは何度も書いて覚えるようにしましょう。

入出力ストリームについての説明は、以上です。

執筆・編集

Tech Fun Magazine編集部
Tech Funの現役のITエンジニアが、システム開発の基礎知識や実践的なノウハウを執筆・編集しています。
Tech Fun ITスクールの研修講師として活躍するメンバーもおり、プログラミング初心者がつまづきやすいポイントを丁寧に解説しています。

ARTICLE
記事一覧

Java基礎

Java12からJava17までに導入された機能の紹介

システム開発の基本

プログラミングとテストの要点

データベース環境構築

データベース環境構築(Windows版) テストデータ作成

データベース環境構築

データベース環境構築(Windows版) MariaDBの設定

データベース環境構築

データベース環境構築(Mac版) テストデータ作成

データベース環境構築

データベース環境構築(Mac版) MariaDBの設定

Java基礎

はじめてのJava

Java基礎

Javaのデバッグ方法

Java基礎

ポリモフィズム

記事一覧を見る