2017-06-01 第8回目 ファイル入出力(2/2)
本日のテーマ
ファイルの入出力
ストリーム(Stream)
Javaで入出力を扱うには,ストリームという概念の理解が必要です. ストリームとは,データを流れとして扱う仕組みです. データがデータソース(Data Source; データの源,入力元)から流れてくる入力ストリームと, データをデータの出力先(Data Destination; 出力先)に流し込む出力ストリームの2つがあります.
下の画像が入力ストリームを表しています. データソースはファイルやネットワークから,もしくは,別のプログラムであるなど, 入力ストリームを扱う側は知らなくても良いようになっています. とにかく,ストリームを扱う側は,必要なデータが流れてくることさえ理解していれば良いわけです. このような性質のため,Javaでは,あらゆる入出力にストリームを利用します.
入力元がファイルであろうと,ネットワークの先であろうと,受け取る方法は同じです. 入力元がどこであろうと,同じように扱うために,ラッピング という作業が必要になります.
下の画像が出力ストリームを表しています.出力も入力ストリームと同じく, ラッピングを利用して,出力先を気にすることなくデータを書き込めます.
ファイル,ネットワーク,また,メモリに書き込むときにもストリームを利用します.
ラッピング(Wrapping)
先ほど,ファイルやネットワーク,さては,メモリの入出力までストリームを利用できると述べました. 異なるものを同じように扱うために,ラッピング(wrapping)と言う概念を利用します. ラッピングは,アダプタパターン(Adapter Pattern)と言われる場合もあります.
ラッピングとは,包むや包装するという意味です.
その意味のとおり,ラッピングを行うには,とある実体を別の型で覆います.
右図のように,別の型で覆うことで,内側の型(Inner)を外側の型(Outer)として扱えるようになります.
// Inner.java
public class Inner{
void method1(){
// some operation...
}
}
// Outer.java
public class Outer{
Inner inner
void method2(){
inner.method1();
}
}
// Main.java
public class Main{
void run(){
Inner inner = new Inner();
Outer outer = this.wrap(inner);
outer.method2();
}
Outer wrap(Inner inner){
Outer outer = new Outer();
outer.inner = inner;
return outer;
}
}
Reader/Writer, InputStream/OutputStream
Javaのストリームを扱う型は下の図のように,Reader,Writer,InputStream,OutputStreamの4種類に大別できます.

上図を縦のグループで見ると,Reader/Writerがテキストデータを
扱う型,InputStream/OutputStreamがバイナリデータを扱う型です.
バイナリデータとは,画像ファイルや音声ファイルなどです.
文字を扱う型では,HTMLやプログラムのソースファイルなど,テキストデータを扱います.
一方,上図を横のグループで見ると,ReaderとInputStreamが入力ストリームを
表しており,WriterとOutputStreamが出力ストリームを表しています.
そして,Readerと呼ばれる型にも複数の型が存在します.Writerも同じく, 複数のWriter型が存在します.
この講義では,テキストデータを扱う型(Reader/Writer)のみを扱います.
ただし,バイナリデータを扱う型であってもテキストデータを扱う型と使い方はほぼ同じです.
import文
4種類のストリームを扱う場合は,import文が必要です.
それぞれの型の先頭にjava.io.をつけたものを import してください.
例えば,Reader と Writerを使いたい場合は,次の2行がプログラムの先頭に必要です.
import java.io.Reader;
import java.io.Writer;
Reader型の例えば,FileReader,BufferedReader を利用するときには,次の2行が必要です.
import java.io.BufferedReader;
import java.io.FileReader;
複数の import 文をまとめるために,import java.io.*;としても構いません.
先ほどのReaderとWriterのimportの2行を1行で書けるようになります.
Reader型
まずは,入力を扱うReaderを利用する方法を確認します.Readerと一口に言っても,
以下のように多くの型がReader型には存在します.
FileReader- ファイル(
File型)からテキストデータを読み込むためのReader型.
- ファイル(
BufferedReader- バッファリングを行うための
Reader型. - データソースから行単位で文字列を読み込める.
- 他の
Readerは文字単位でしかデータを読み込めない.
- 他の
- バッファリングを行うための
StringReader- 文字列(
String型)からテキストデータを読み込むためのReader型.
- 文字列(
LineNumberReader- 読み込んだ行数を数える
Reader型.
- 読み込んだ行数を数える
InputStreamReaderInputStream型の入力ストリームをReader型に変換するReader型.
ここに挙げた以外にもいくつかのReader型が存在しますが,この講義では,FileReader
とBufferedReader,そして,InputStreamReaderの3つの型を扱います.
また,それぞれの型を利用するには,それぞれの型のインポートが必要です.
例えば,BufferedReaderを利用するときには,import java.io.BufferedReader;という一文が
クラス宣言の前に必要です.
典型的なファイルからのデータの読み込み方法.
void readMethod(File file) throws IOException{
BufferedReader in = new BufferedReader(new FileReader(file)); // (1)
// 上記の(1)の処理を区別して書くと,次のような処理になる.
// FileReader freader = new FileReader(file);
// BufferedReader in = new BufferedReader(freader);
String line;
while((line = in.readLine()) != null){
// 1行ずつ処理を行う.
}
in.close();
}
ファイルからデータを1行ずつ読み込む典型的な方法は,上記の通りです.
まず FileReaderを利用してファイルから読み込むための Readerを構築(new)します.
次に,構築した FileReaderをBufferedReaderに渡し,
行単位で読み込めるBufferedReaderを構築します.
そして,1行ずつ読み込むには,BufferedReader型が持つreadLineメソッドを
呼び出します.readLineメソッドは,1行読み込み,その結果をString型で返します.
もう読み込めるデータがない場合は,nullを返します.
そのため,while((line = in.readLine()) != null) という繰り返しで最後まで順に読み込めるようになります.
最後にこれ以上ストリームを利用しないため,closeメソッドを呼び出します.close
メソッドは一番外側のReaderに対してだけ呼び出せば良いです.
ラップされているReaderは外側のReaderが閉じられると,自動的に閉じられます.
入出力を扱うには,必ず IOExceptionという検査例外に対応しなければいけません.
どこかで入出力エラーが起こる可能性が排除できないためです.
そのため,メソッドのシグネチャに,throws節を追加しています.
もちろん,readMethodメソッドを呼び出しているメソッドにも throws
節で,IOExceptionの責任を転嫁してください.
この点に不安がある人は,例外機構を復習してください.
ただし,FileReaderに渡すファイルが存在しない場合
(existsメソッドがfalseを返す場合),FileNotFoundExceptionという例外が投げられます.
なお,FileNotFoundExceptionはIOExceptionとしても扱えますので,throws
節はIOExceptionのみで問題ありません.
1文字単位の読み込み
1行単位で読み込むには,典型的なファイルからのデータの読み込み方法のようなプログラムで可能です. 一方で,1文字単位で読み込みたい場合もあります. その場合の典型例を以下に示します.
void readMethod(String targetFile) throws IOException{
FileReader in = new FileReader(targetFile); // File型でもString型でもOK
Integer read;
while((read = in.read()) != -1){
// 1文字単位で読み込む.この場合,ラッピングはしてもしなくてもOK.
}
in.close();
}
Javaの Reader 型には,1文字単位で読み込むメソッドreadが用意されています.
読み込んだ文字を Integer 型で返します.
読み込むデータがもう存在しない場合は,-1が返されますので,その間繰り返す,というプログラムになっています.
例題1. Catコマンドの作成
コマンドラインで指定されたテキストファイルの内容を表示するコマンド Catを作成してください.
この例題では,コマンドライン引数が与えられなかった場合は考える必要はありません.
実行例
$ cat Cat.java
import java.io.*;
...途中省略
}
$ java Cat Cat.java
import java.io.*;
...途中省略
}
Writer型
出力を扱うWriterを利用する方法を確認しましょう.Writer型もReader型と同じく,
多くの型が存在します.典型的な型を次に紹介します.
FileWriter- ファイル(
File型)に書き込むためのWriter型.
- ファイル(
PrintWriterprintfやprintln,printが利用できるWriter型.- 他の
Writer型は文字単位でしか出力できない.
- 他の
StringWriter- 文字列(
String型)に書き込むためのWriter型.
- 文字列(
OutputStreamWriterOutputStream型をWriter型に変換するためのWriter型.
ここに挙げた以外にもいくつかのWriter型が存在しますが,この講義では,FileWriter,
PrintWriter の2つの型のみを利用します.
典型的なファイルへのデータの書き込み方法.
void writeMethod(File file, String message) throws IOException{
PrintWriter out = new PrintWriter(new FileWriter(file)); // (2)
// 上記の(2)の処理を区別して書くと次のような処理になる.
// FileWriter fwriter = new FileWriter(file);
// PrintWriter out = new PrintWriter(fwriter);
out.print(message);
out.close();
}
ファイルに文字列(String型の変数message)を書き込む方法は上記の通りです.
まず,FileWriterを利用してファイルに書き込むためのWriter型を構築します.
次に,構築したFileWriterをPrintWriterに渡し,文字列の出力が
可能なPrintWriterを構築します.
そして,文字列をファイルに書き込むには,PrintWriter型の変数outに対してprintメソッドを呼び出して,文字列を書き込んでいます.
他に,printlnやprintfメソッドも用意されています.
最後に,これ以上ストリームを利用しないため,closeメソッドを呼び出しています.closeメソッドを
呼び出すと,ラップされているFileWriterも閉じられます.
読み込み時と同じように,IOExceptionの例外に対応しなければいけません.
ここでも同じように,writeMethodを呼び出しているメソッドに責任を転嫁するため,throws節
でIOException を投げると宣言しましょう.
1文字単位で書き込む場合は,Writer型のwriteメソッドを利用してください.write
メソッドにInteger型の値を渡せば適切な出力先に書き込まれます.
1文字単位の読み込みで取得した Integer型の値を渡すのが典型的な使い方でしょう.
例題2. 指定された行数を出力するコマンド
ここでは,ファイルに値を書き出してみましょう.クラス名をOutputNとし,
コマンドライン引数で数字とファイル名を受け取ってください.
コマンドライン引数で受け取った数字を1から順にカウントアップして指定行数を
指定されたファイルに出力してください.
実行例
$ ls
OutputN.java OutputN.class
$ java OutputN 3 file3.txt
$ ls
OutputN.java OutputN.class file3.txt
$ cat file3.txt
1
2
3
$ java OutputN 10 file10.txt
$ ls
OutputN.java OutputN.class file3.txt file10.txt
$ cat file10.txt
1
2
...途中省略
9
10
- 最初は何もファイルがありません.
java OutputN 3 file3.txtを実行して,file3.txtに値を書き込んでいます.OutputNの実行時に3が渡されていますので,1, 2, 3とカウントアップした値が各行に出力されています.cat file3.txtで内容の確認をしています.
java OutputN 10 file10.txtを実行して,file10.txtに値を書き込んでいます.cat file10.txtで内容の確認をしています.
練習問題
1. 行番号付きのCatコマンドの作成
例題1を改良し,行番号付きで出力する cat コマンドを作成してください.
クラス名はCat2としてください.
ただし,例題1と異なり,複数のファイルを指定できるようにしましょう.
この例題でも,引数は必ず与えられるものとしてください.
出力例
$ cat -n Cat2.java
1 import java.io.*;
...途中省略.
23 }
$ java Cat2 Cat2.java Cat.java
1 import java.io.*;
...途中省略.
23 }
1 import java.io.*;
...途中省略.
19 }
2. Grepコマンドの作成
ここでは,grepコマンドを作成しましょう.grepコマンドとは,
キーワードと1つ以上のファイル名が与えられます.
ファイルの行にキーワードが含まれていれば,その行を出力するコマンドです.
クラス名はGrepとしてください.
結果出力には,以下の出力例のようにファイル名も含めてください.
複数のファイルが与えられたとしても,検索できるようにしましょう.
なお,キーワードは省略されることはなく,ファイルは少なくとも1つは指定されるとして構いません.
文字列にある文字列が含まれているかを確認する
ある文字列(stringA)に,別の文字列(stringB)が含まれているかを
確認するには,containsメソッドを利用してください.
String stringA = "this is a pen";
String stringB = "is a";
String stringC = "are";
if(stringA.contains(stringB)){
System.out.println("このメッセージは表示される.");
}
if(stringA.contains(stringC)){
System.out.println("このメッセージは表示されない.");
}
出力例
$ grep line Cat.java
String line;
while((line = in.readLine()) != null){
System.out.println(line);
$ java Grep line Cat.java
Cat.java: String line;
Cat.java: while((line = in.readLine()) != null){
Cat.java: System.out.println(line);
$ java Grep class Cat.java Cat2.java
Cat.java: public class Cat{
Cat2.java: public class Cat2{
3. Headコマンドの作成
指定された行数だけファイルの先頭から出力するコマンドheadを作成しましょう.
コマンドライン引数では,行数とファイル名を受け取ってください.
ただし,ファイル名は省略可能です.ファイル名が省略された場合,標準入力から読み込むようにしてください.
クラス名は Headとしてください.
コマンドライン引数が1つしか与えられなかった時に,標準入力(System.in)から受け取るようにするには,
次のようなコードで BufferedReaderを構築してください.
標準入力,標準出力については,基礎プログラミング演習IIの講義資料を確認してください.
BufferedReader in;
// コマンドライン引数が1つしか与えられなかった場合.
if(args.length == 1){
in = new BufferedReader(new InputStreamReader(System.in));
}
else{
in = new BufferedReader(new FileReader(args[1]));
}
出力例
$ head -3 Cat.java
import java.io.*;
public class Cat{
$ java Head 3 Cat.java
import java.io.*;
public class Cat{
$ cat Cat.java | java Head 4
import java.io.*;
public class Cat{
void run(String[] args) throws IOException{
cat Cat.java | java Head 4 とコマンドを実行した時に,標準入力から入力を受け取ることになります.
そうでない場合(ファイルをコマンドライン引数で指定した場合)は,java Head 3 Cat.java
のようなコマンドの入力になります.
4. Teeコマンドの作成
ここでは,teeコマンドを作成しましょう.teeコマンドは
標準入力で受け取った文字列を標準出力と,指定されたファイルに出力するコマンドです.
以下の図のように T の形に入力を分配するところから名付けられています.クラス名をTeeとしてください.

コマンドライン引数にファイル名を受け取ってください.
また,標準入力から値を受け取るようにしましょう.上記のようにBufferedReaderを
構築した後は,Catの時と同じように入力を受け取れば良いです.
入力が終わればnullが返ってきますので,自動的にループを抜けるようになっています.
出力例
$ cat hoge
cat: hoge: No such file or directory
$ cat Cat.java | java Tee hoge
import java.io.*;
...途中省略
}
$ cat hoge
import java.io.*;
...途中省略
}
まとめ
- Javaでの入出力はストリームという流れで表される.
- 入力はデータソース(Data Source)からデータが流れてくる.
- 出力は出力先(Data Destination)にデータを流し込む.
- 利用するときは,ラッピング(Wrapping)を行う.
- Javaでは,4種類のストリームが用意されている.
- 文字データの入力は
Reader,- 各種
Reader型- ファイルからテキストデータを読み込む
FileReader型. - バッファリングしてテキストデータを読み込む
BufferedReader型. - 文字列からテキストデータを読み込む
StringReader型. - 読み込んだ行数を数える
LineNumberReader型. InputStream型をReaderに変換するInputStreamReader型.
- ファイルからテキストデータを読み込む
Readerの典型的な使い方.
- 各種
- 文字データの出力は
Writer,- 各種
Writer型- ファイルにテキストデータを書き込む
FileWriter型. printf,println,printを利用できるPrintWriter型.- 文字列にテキストデータ書き込む
StringWriter型. OutputStream型をWriterに変換するOutputStreamWriter型.
- ファイルにテキストデータを書き込む
Writerの典型的な使い方.
- 各種
- バイナリデータの入力は
InputStream, - バイナリデータの出力は
OutputStream. - 使用するときには,
importが必要.
- 文字データの入力は
- 文字列にある文字列が含まれているかを確認する
stringA.contains(stringB)stringAにstringBが含まれていればtrue,含まれていなければfalse.

