はじめに
プログラムは処理するデータを外部から貰う必要があります。またプログラムで処理した結果はどこかに保存しなければなりません。これが入出力処理です。これまでは入力はキーボード、出力はコンソールだけでしたが、この章からはファイルへの入出力を学習します。
またオブジェクトをファイルに記録する方法も学習します。文字や数値などではなく、オブジェクトそのものを処理対象として入出力できると、効率の良いプログラムを書けるようになります。
目次
- ファイル入出力クラス
- Fileクラスの操作
- ファイル出力
- ファイル入力
- シリアライズとデシリアライズ
- transient修飾子
- 継承と直列化
- 章のまとめ
目標
ファイルからデータを読み書きする方法を理解すること。またオブジェクト単位で入出力処理できるようにすること。
ファイル入出力クラス
コンピュータに接続する機器が多岐にわたるため、プログラミングでは入出力処理があります。
- キーボードをタイプして入力し、処理結果をコンソールに出力する。
- 記憶装置のファイルを読み書きする。
- マウスやスキャナなど周辺機器からデータを入力する
- ウェブなどから送信されてくるデータを受信し、応答を送信する。
- データベースのレコードを読み書きする。
Java言語ではこれらの処理を行うクラスがJDKに付属して配布されています。
それらはいくつかのパッケージにまとめられています。
- Java.ioパッケージ
- Javax.servlet、java.netパッケージ
- Java.sqlパッケージ
この章で扱う入出力は、記憶装置のファイルを読み書きする
処理です。
ファイル入出力にはデータを単なる0と1の並びとして扱うバイトストリーム
と文字データとして扱う文字ストリーム
があります。
なお、ストリーム
という呼び方は入出力を行う装置や仕組みに対して、データを時間の流れ(stream
)と捉える考え方に由来します。
文字ストリーム
文字ストリームに関連するクラスは以下の6つのクラスです。
Fileの名前を扱うクラス
- Fileクラス
Fileクラスはファイル名を表すクラスです。ファイル入出力を行うとき、「どのファイルか」を指定するのに使われます。また、Fileクラスのメソッドを使うとファイルの新規作成、名前変更、削除などができます。
File出力クラス
- FileWriterクラス
ファイルへの書き込みメソッドを持つクラスです。writeメソッドを使って文字列をファイルに書き込むことができます。効率よく書き込みを行うためにBufferedWriterと組み合わせて利用します。
- BufferedWriterクラス
書き込まれるデータをそのまま記憶装置に出力せず、メモリーに蓄えておいて一定量が溜まった時に一度にまとめて書き込むようにすることをバッファリングといいます。
記憶装置は動作の低速な機器ですからバッファリングによりアクセス回数を減らすことで効率良い書き込みができます。このクラスをFileWriteと組み合わせると、出力をバッファリングして効率の良いファイル出力を行うことができます。
- PrintWriterクラス
高機能なメソッドを持つクラスです。FileWriter、BufferedWriterとともに併用し、出力処理の多くはPrintWriterで実行します。また、Java5から機能が拡充されPrintWriter単体でも使えるようになりました。
File入力クラス
- FileReaderクラス
ファイルからの読み込みメソッドを持つクラスです。readメソッドを使って文字列をファイルから読み出すことができます。1文字単位での読み出しが基本の動作です。効率よく読み込みを行うためにBufferedReaderと組み合わせて使用します。
- BufferedReaderクラス
ファイルから一度に大量のデータを読み込んでバッファに貯めておき、実際の読み込み処理に対してバッファからデータを返すことにより効率の良い読み込み処理を実現します。このクラスをFileReaderと組み合わせると、入力をバッファリングして効率の良いファイル入力を行うことができます。
Fileクラスの操作
ファイルクラスにはたくさんのメソッドがありますが、そのうち特に重要なメソッドは以下です。
クラス名 | 主なメソッド | メソッドの機能 |
---|---|---|
File | mkdirs() | ディレクトリを作る |
File | exists() | ファイル・ディレクトリの存在をチェックする |
File | list() | ディレクトリ内のファイル名のリストを返す |
File | isFile() | ファイルかどうか調べる |
File | isDirectory() | ディレクトリかどうか調べる |
mkdirs(ディレクトリを作る)
新たにMkdirsExec
クラスを作成し、実際に以下を入力してください。
項目 | 名前 | 備考 |
---|---|---|
プロジェクト | alj_study | |
パッケージ | chapter12 | |
クラス名 | MkdirsExec |
package chapter12; |
コンパイル例:javac chapter12¥MkdirsExec.java
実行結果:java chapter12.MkdirsExec
結果:true
mkdirsメソッドは、ディレクトリが存在していない場合には、ディレクトリを作成します。作成が成功すると true を返します。失敗すると falseを返します。
※ mkdirsのメソッドの詳細については API 仕様 をご確認ください。
File.separator
は、Windowsの場合は¥
、LinuxやUnixの場合は/
が代入されています。separator
を使っておくと、OSが違っていてもプログラムを書き換える必要がありません。
exists(ファイル・ディレクトリの存在をチェックする)
existsメソッドを使うとファイルやディレクトリが存在するかとうか検査できます。
新たにExistsExec
クラスを作成し、実際に以下を入力してください。
項目 | 名前 | 備考 |
---|---|---|
プロジェクト | alj_study | |
パッケージ | chapter12 | |
クラス名 | ExistsExec |
package chapter12; |
コンパイル例:javac chapter12¥ExistsExec.java
実行結果:java chapter12.ExistsExec
mydataは存在する。
existsメソッドはファイルやディレクトリの存在を検査するのに使います。パスは絶対パスを指定します。
※ existsメソッドの詳細については API 仕様 をご確認ください。
list、isFile、isDirectory(ディレクトリの内容を表示する)
listメソッドを使うと、ディレクトリの中にある全てのファイル名とディレクトリ名をStringの配列として取得することができます。ただし、ファイル名とディレクトリ名が混在している場合は、どれがファイル名でどれがディレクトリ名かを区別する必要があります。isFileメソッドを使うとファイルがどうか判定できます。またisDirectoryメソッドを使うとディレクトリかどうか判定できます
新たにFileListExec
クラスを作成し、実際に以下を入力してください。
項目 | 名前 | 備考 |
---|---|---|
プロジェクト | alj_study | |
パッケージ | chapter12 | |
クラス名 | FileListExec |
package chapter12; |
コンパイル例:javac chapter12¥FileListExec.java
実行結果:java chapter12.FileListExec mydata
mydata
を引数に指定すると、mydata
ディレクトリ配下にあるファイル一覧が表示されます。mydata
ディレクトリ配下には、ファイルが存在しない為、何も表示はされません。
実行結果:java chapter12.FileListExec chapter12
FileListExec.java
ExecMkdirs.java
ExistsExec.class
ExistsExec.java
FileListExec.class
ExecMkdirs.class
chapter12
を引数に指定すると、chapter12
ディレクトリの配下にあるファイルが一覧で表示されます。
※ listメソッド、isFileメソッド、isDirectoryメソッドの詳細については API 仕様 をご確認ください。
ファイル出力
ファイルへ文字列を出力するにはFileWriter、BufferedWriter、PrintWriterを使います。
ラップ
ファイルへ文字を出力するとき、直接的にはBufferedWriterを使います。それはバッファリング機能で効率よく出力でき、1行分ずつ出力できるメソッドがあるなど使いやすいからです。しかし、BufferedWriterにはファイルへ文字を書く能力はなく、その部分はFileWriterに処理を依頼します。バッファにデータが溜まったところを見計らい、FileWriterに送って一度出力します。
この関係を概念的に表したのが以下の図です。
オブジェクトのレベルで考えると、この関係は次のようになります。BufferedWriterはフィールドにFileWriterの参照を持ちます。これにより、必要な時、FileWriterの出力メソッドを使うことができます。
このように、他のオブジェクトの参照を内部に持つことをHAS-A関係
といいます。
実際のプログラムでは以下のようにコーディングします。
FileWriter fw = new FileWriter("test.txt"); |
上記の書き方は、以下のように1行に短縮することが可能です。
BufferedWriter bw = ew BufferedWriter(new FileWriter("test.txt")); |
上記の書き方はBufferedWriterがFileWriterを包み込むように見えます。そのためこれをBufferedWriterで FileWriterFイェWリテrをラップする
といいます。
しかし、文字列出力の場合、最も使い勝手の良いのはPrintWriterです。PrintWriterは書式付出力やテキストの行単位の処理など、高機能なメソッドを持つクラスです。使い方としては以下のようにBufferedWriterをさらに以下のようにラップ
します。
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("test.txt"))); |
新たにPrintWriterExec
クラスを作成し、実行結果を確認して下さい。
項目 | 名前 | 備考 |
---|---|---|
プロジェクト | alj_study | |
パッケージ | chapter12 | |
クラス名 | PrintWriterExec |
package chapter12; |
test.txtは以下の手順で作成してください。
プロジェクトで右クリックします。そして [新規]-[表題なしのテキストファイル] を選択します。
ファイルを保存します。[command] + [s] を押し、ファイル名を「test.txt」にします。
出力結果
入出力処理は一般にチェック例外を発生します。そのためtry-catchで囲まないとコンパイルエラーになります。多くの場合発生する例外はIOException
です。
「ファイルを開く」というのは、データを書き込むための初期化作業にあたります。PrintWriterオブジェクトを作成することが「ファイルを開く」ことです。この時、指定されたファイルが存在しなければ自動的に作成します。
出力にはprintlnやprintメソッドを使用します。上記の場合pw.println("ALJ Education Plus 株式会社");
、pw.println("テスト");
で2行、test.txtファイルへ出力しています。
最後にcloseメソッドを忘れないようにしてください。ファイルへの出力はバッファリングされているので、closeしないとメモリー上のバッファにデータが残ったままになります。また、closeはファイル出力のために確保したバッファメモリなどを解放します。システムの資源を解放するという点からも必ず実行してください。
ファイル入力
ファイルからデータを読み出すには、FileReader
とBufferedReader
を使います。次のようにFileReaderをBufferedReaderでラップします。
BufferedReader br = new BufferedReader(new FileReader("test.txt")) |
読み込みは以下のようにreadLineメソッドを使って一行単位で読み出します。データを最後まで読み出してしまうとreadLineメソッドはnullを返すので次のようなwhile文を使います。
String str; |
新たにBufferedReaderExec
クラスを作成し、実際に以下を入力してください。
※test.txtはPrintWriterExecクラス
を実行した際に使用したものを使います。
package chapter12; |
確認結果
上記の例は典型的な読み込みのパターンです。1行分ずつデータを読み取り、それをコンソールに表示する処理を繰り返します。readLineがnullを返すとそれがデータの最後を示します。
シリアライズとデシリアライズ
オブジェクトをファイルなどに出力して保存することを直列化(シリアライズ)
といいます。また、シリアライズで記録したファイルの内容を読み取ってオブジェクトを元どおり復元することを直列化復元(デシリアライズ)
といいます。
直列化可能なクラスはSerializableインターフェースを実装するルールになっています。インターフェースの内容は以下です。
public interface Serializable{} |
Serializableインターフェースの中身は何も定義されていません。これは直列化可能であることを宣言するために使われています。このようなインターフェースをマーカーインターフェース
といいます。
直列化と直列化復元を行うクラスはObjectInputStreamクラス
とObjectOutputStreamクラス
です。FileInputStream
とFileOutputStream
をこれらでラップして使用します。
クラス構成図は以下です。
シリアライズ
まずは実際にオブジェクトを作成し、ファイルを保存してみましょう。
新たにObjectBook
クラスとObjectBookWriterExec
クラスを作成してください。
ObjectBook.javapackage chapter12;
import java.io.Serializable;
public class ObjectBook implements Serializable {
//シリアルバーション番号付与
private static final long serialVersionUID = 1L;
//フィールド変数
private String subject;
private String author;
private int price;
//コンストラクタ
ObjectBook(String subject,String author, int price){
this.subject = subject;
this.author = author;
this.price = price;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
ObjectBookWriterExec.javapackage chapter12;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjectBookWriterExec {
public static void main(String[] args) {
//直列化するオブジェクトを作成
ObjectBook ob = new ObjectBook("Java","ALJ",1000);
try {
//ファイルを開く
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.txt"));
//オブジェクトを出力する
out.writeObject(ob);
//ファイルを閉じる
out.close();
} catch (FileNotFoundException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
}
}
出力結果
直列化するクラス(ObjectBookクラス)には通常、シリアルバーション番号をつけます。常に以下のように宣言してください。
private static final long serialVersionUID = 1L; |
シリアルバーション番号は保存時と読み出し時に比較され、違う番号になっていると互換性がないと判断され、例外が発生します。
※シリアルバーション番号が宣言されないとコンパイラが適当につけてくれますが、ロジックを変えてしまうだけでも新たに別の番号が付与されてしまうので、明示的に付与するしてください。
ObjectBookWriterExecクラスの以下の箇所は、典型的なオブジェクト出力処理の書き方です。
//ファイルを開く |
上記のように、FileOutputStreamをObjectOutputStreamでラップします。引数に出力ファイルを指定します。
writeObjectメソッドでオブジェクトを出力します。引数にオブジェクトの参照を指定します。そしてcloseメソッドでファイルを閉じます。
デシリアライズ
次はファイルに保存したオブジェクトを読み出して復元する例です。
ObjectBookReadExec
クラスを作成してください。
ObjectBookReadExec.javapackage chapter12;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectBookReadExec {
public static void main(String[] args) {
ObjectInputStream in;
try {
in = new ObjectInputStream(new FileInputStream("book.txt"));
ObjectBook ob = (ObjectBook)(in.readObject());
System.out.println("名前=" + ob.getSubject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e){
e.printStackTrace();
}
}
}
以下が復元処理(直列化復元、デシリアライズ)です。
in = new ObjectInputStream(new FileInputStream("book.txt")); |
FileInputStream
をObjectInputStream
でラップして使用しています。
次に、readObjectメソッドでオブジェクトを読み出します。このメソッドの戻り値型はObject型なのでObjectBook
型の参照にキャストしています。
実行すると以下のようにコンソールへ出力されます。
出力結果
transient修飾子
全てのクラスが直列化できるわけではありません。何らかの理由で直列化できないクラスの参照をフィールドとして持つクラスは、そのままでは直列化できないことになります。
そこで、「このフィールドは直列化しなくて良い」と宣言するのがtransient修飾子
です。
transient修飾子をつけた変数は、直列化から除外されます。これによりクラスの残りの部分を直列化することが可能です。直列化から除外されたフィールドは、かなのツヨゥレツ復元時に既定値で初期化されます。コンストラクタでの初期化は実行されないので注意してください。
先ほど作成したObjectBookクラス
のフィールド変数「subject」にtransient
を付与してください。
package chapter12; |
先ほど作成したObjectBookWriterExecクラス
を実行してみてください。
package chapter12; |
そしてObjectBookReadExecクラス
を実行してみてください。
package chapter12; |
実行結果
継承と直列化
親クラスがSerializableインターフェースを実装していれば、サブクラスは特に実装しなくても暗黙のうちにSerializableインターフェースを実装していることになります。
一方、サブクラスだけがSerializableインターフェースを実装し、スーパークラスが実装していない場合でも、サブクラスを直列化することができます。ただ、直列化復元時に、スーパークラスから引き継いだフィールドは、全てコンストラクタによりデフォルトに初期化されます。
直列化復元では通常はコンストラクタは起動しません。しかし、Serializableを実装していない親クラスから引き継いだ部分についてはコンストラクタが起動し、初期化します。
Parent.javapackage chapter12;
//Serializableを実装していないスーパークラス
public class Parent {
int i;
Parent(){
this.i = 100; //初期値
}
}
Child.javapackage chapter12;
import java.io.Serializable;
public class Child extends Parent implements Serializable{
private static final long serialVersionUID = 1L;
String name ;
public Child(int i , String name){
this.i = i;
this.name = name;
}
}
package chapter12; |
実行結果
実行結果を見るとわかるように、サブクラスのname
は復元されて山田
となっていますが、スーパークラスから引き継いだi
はコンストラクタで初期化された20
ではなく、100
に戻っていることがわかります。
なお、static
をつけて宣言したクラス変数は、オブジェクトの中に含まれていない為、直列化の対象とはなりません。
章のまとめ
以下の要点をしっかり理解してから次の章へ進んでください。
Fileクラス
はファイル名を表すクラスである。- ファイル入出力には
バイトストリーム
と文字ストリーム
がある。 - 入力処理に使うのは
FileReader
、BufferedReader
である。 - 実際に入力処理を行う際は、FileReaderをBufferedReaderで
ラップ
する。 - BufferedReaderクラスでは
readLineメソッド
で一行単位に読み出す。 - 出力処理に使うのは
FileWriter
、BufferedWriter
、PrintWriter
である。 - 実際に出力処理を行う際は、FileWriterをBufferedWriterで
ラップ
し、さらにPrintWriter
でラップ
する。 - PrintWriterクラスは
printlnメソッド
、printメソッド
で出力することができる。 - ディレクトリを作るときは、Fileクラスのメソッド
mkdirs()
を使う。 - ファイル・ディレクトリの存在をチェックするときは、Fileクラスのメソッド
exists()
を使う。 - ディレクトリ内のファイル名のリストを返すときは、Fileクラスのメソッド
list()
を使う。 - ファイルかどうか調べるときは、Fileクラスのメソッド
isFile()
を使う。 - ディレクトリかどうか調べるときは、Fileクラスのメソッド
isDirectory()
を使う。 - オブジェクトを復元可能な状態でファイルに出力することを
直列化(シリアライズ)
という。 - 直列化したファイルからオブジェクトを復元することを
直列化復元(デシリアライズ)
という。 - 直列化するクラスは
Serializableインターフェース
をimplementsしなければならない。 - 直列化するためには、まずは
FileOutputStream
をObjectOutputStream
でラップする。そしてObjectOutputStreamクラスのwriteObjectメソッド
を使用して直列化を行う。 - 直列化復元するためには、まずは
FileInputStream
をObjectInputStream
でラップする。そしてObjectInputStreamクラスのreadObjectメソッド
を使用して直列化復元を行う。 transient修飾子
をつけることでメンバーを直列化の対象からはずすことができる。- スーパークラスがSerializableインターフェースを実装していれば
サブクラス
もSerializableインターフェース
を実装していることになる。 - Serializableインターフェースを実装しないスーパークラスから継承したフィールドは
直列化されない
。 - Serializableインターフェースを実装しないスーパークラスから継承したフィールドは
復元時にコンストラクタで初期化
される。 static
をつけて宣言したクラス変数は、直列化の対象とはならない。