プログラミング応用b 第7回『ファイル入出力の基礎』07-04. 『文字データ出力系ストリーム』


■4. 『文字データ出力系ストリーム』

 では,文字データ出力系ストリームの概要を見てみよう。Fig.4に,文字データ出力ストリーム
のクラス間の継承関係を示す。前節で紹介した文字データ出力ストリーム群のクラス間関係と比較すると,
多くの類似点が見つかるはずである。文字データ出力系ストリームのスーパークラスは Writer である。
このため,文字データ出力ストリームのことを,単に“ライタ”と呼ぶこともある。
 文字データ出力ストリームも,文字データ入力ストリームと同じように
,大きく基本ストリームとラッパ
ストリームにわけることができる
。(下図をクリックすると授業で扱わないクラスは非表示になる)


出力系ストリームにも「基本ストリームとラッパストリームがある」,ということは,
入力系ストリーム同様,下図の様な出力ストリームの珠々繋ぎが可能であると言うことである。
入力系ストリームと同じ様に,基本ストリーム,ラッパーストリーム,それらを珠々繋ぎにした状態を紹介する。
細かい部分まで読まなくても,入力系ストリームの場合と同じアイディアであることを押さえれば,理解は容易だろう。


・文字データ出力系の基本ストリーム


・文字データ出力系のラッパーストリームの姿


・基本ストリームに複数のラッパストリームを珠々繋ぎにして行う文字データ出力の仕組み


ここで,出力ストリームオブジェクト(ライタ)を初期化し,そのストリームをオープンする
コンストラクタの引数について以下の表にまとめておく。

文字出力ストリーム(ライタ)の種類 コンストラクタへの引数
基本ストリーム 最終出力先を指定する情報 (例:ファイルを最終出力先とするFileWriter
クラスの場合,ファイル名などファイルを指定する情報)
ラッパーストリーム 出力先となるストリームオブジェクトへのWriter型参照値。



■4.1. 『スーパークラスWriter』

 前述したように,すべての文字データ出力ストリームのスーパークラスとして,抽象クラス Writer
が用意されている。このため,文字データ出力ストリームのことを,単に“ライタ”と呼ぶこともある。
 抽象クラス Writer のメソッドを Table 4 に示す。メソッド close( ) は,そのストリームが使い終
わったときに呼び出してストリームを閉じる。flush( ) は,それまで出力した内容が確実に出力先に
反映されるようにバッファに残ったデータを出力先に書き出す。
 Writer で特徴的なのは,write( ) メソッドである。write( ) メソッドによって,文字・文字列データ
を書き込むことになる。




■4.2. 『文字出力用の基本ストリーム』

 では,文字データ出力ストリーム群のうち,まずは基本ストリームから解説していこう。授業で
扱うのは OutputStreamWriter と FileWriter だけである。

・OutputStreamWriter

 OutputStreamWriter は, InputStreamReader の出力版で,

  バイトデータ出力系の基本ストリーム OutputStream

を出力先としてラップして,Java内部文字コード『UTF-16 ビッグエンディアン』から,別のエン
コーディングへ変換した文字データを書き込む機能を持っているライタである。
コンストラクタと独自メソッドをTable 5-(4)と下表に示す。指定できる文字コード名はこちら

OutputStreamWriter の主なコンストラクタ
※実態はラッパストリームなので,(OutputStream型の)出力先ストリームオブジェクトへの参照値を(第1)実引数に取る
 ※第2実引数として,第1実引数で指定された出力先ストリームへ出力する文字コードを指定する情報を取る
これらの実引数によって初期化されストリームをオープンする。

 OutputStreamWriter( OutputStream out,            String charsetName )

 OutputStreamWriter( OutputStream out,            Charset cs )

 OutputStreamWriter( OutputStream out )

OutputStream型ストリーム out を出力先とするストリームを生成・初期化する。

文字エンコーディングを charsetName や Charset で指定すれば,Javaの内部文字コード
『UTF-16 ビッグエンディアン』で書き込んだデータを指定エンコーディングへ変換して
出力先に出力する。文字コーディングが指定されない場合は,デフォルトエンコーディング
が指定されたものとみなされる。

※なお,エンコーディング名を指定するコンストラクタは,指定されたエンコーディングがサポートされて
 いないときは,UnsupportedEncodingException (IOExceptionのサブクラス) 型の例外を投げる。


OutputStreamWriter の主なメソッド


 本授業では,上で紹介した Writer から継承したメソッドだけ知っていれば良い。


 出力と入力の違いはあるが,要は文字入力系ストリームの InputStreamとInputStreamReader
の関係と同じである(下図)。OutputStreamWriterはこのように,構造的にはラッパストリームだが,
役目的には文字データ出力系ストリームの基本ストリームと言っていい。
 



■FileOutputStream

 なお,( OutputStreamWriterの最終出力先である) バイトデータ出力系の基本ストリーム OutputStream には,接続する
実際の最終出力先ごとにサブクラスが用意されている

 ここで,バイトデータ出力系ストリームのうち,今回話しに登場するクラスの継承関係の図を以下に示す(図をクリックすると
イトデータ出力系ストリームのすべてのクラスを表示する)。これらのクラスは java.io パッケージ所属である
下図でもわかるとおり,ファイルを最終出力先とするバイトデータ入力系の基本ストリームとしては,FileOutputStream
定義されているので
,ファイルに文字を書き込むときにはこれを OutputStreamWriterの出力先として利用する。


FileOutputStream のコンストラクタを下表に示す。

FileOutputStream のコンストラクタ
※基本ストリームなので最終出力先(ここではファイル)を示す情報をコンストラクタの実引数として渡してストリームをオープンする。
 FileOutputStream ( String name )
 FileOutputStream ( String name,            boolean append )

 FileOutputStream ( File file )
 FileOutputStream ( File file, boolean append )

 FileOutputStream ( FileDescriptor fdObj )


これらのコンストラクタは,
 ・String型のファイルパス名文字列 name
 ・File型オブジェクト file
 ・FileDescriptor(ファイル記述子)型オブジェクト fd
で指定されたファイルを(バイトデータの出力先とするバイトデータ出力の基本ストリームとして)書き込み用にオープンする。

第2引数appendを持つものは,append が true の場合,指定されたファイルの内容の末尾から追記する形でファイルを
オープンする。

※指定されたファイルがディレクトリだったり,書き込み用にファイルを新規作成できない場合などの理由でオープンできない
 ときは,FileNotFoundException (IOExceptionのサブクラス) 例外を投げる。
※セキュリティ上の制限で読み込みアクセスが拒否された場合,SecurityException (RuntimeExceptionのサブクラス )
 例外が投げられる

※File型とFileDescriptor型については後で紹介する。



■FileWriter

 FileWriter は,
  ・ファイルを出力先とする
  ・ファイルに書き込むときにはデフォルトエンコーディング文字に変換して書き込む
というライタである。コンストラクタと主なメソッドをTable 5-(3)および以下にの表に示す。
 なお,Fig.4に示すように FilterWriter は,前述した OutputStreamWriter (Table 5-(4))
のサブクラスなので,そのメソッドも継承していることに注意するようにしよう。

FileWriter のコンストラクタ
 FileWriter( String name )
 FileWriter( String name,
      boolean append )

 FileWriter( File file )
 FileWriter( File file, boolean append )
 FileWriter( FileDescriptor fd )


これらのコンストラクタは,
 ・String型のファイルパス名文字列 name
 ・File型オブジェクト file
 ・FileDescriptor(ファイル記述子)型オブジェクト fd
で指定されたファイルを最終出力先とする文字出力ストリームをオープン
する。第2引数 append の値が true なら追加書き込みでオープンする。

※なんらかの理由でオープンに失敗すると,IOException例外を投げる。


FileWriter の主なメソッドは以下の通り。

FileWriter の主なメソッド


 本授業では,上で紹介した Writer と OutputStreamWriter から継承したメソッドだけ知っていれば良い。

  上述したように, FileWriter は直前に紹介した OutputStreamWriter のサブクラスなので,厳密
に言えば基本ストリームとは言えない。しかし, FileWriter は指定されたファイルを FileOutputStream
で開いて,それをラップする形で出力を行うので,利用者はラッパライタであることを意識する必要
はない。そのため,Fig.4では, FileWriter を基本ストリームに分類している。

 FileWriterの特徴は,

 ・出力先に文字データを出力する際に,Javaの内部文字コード『UTF-16ビッグエンディアン』から文字コードを
  デフォルトエンコーディングに変換して書き込む。
  ※デフォルト文字エンコーディングは,使用しているJava環境の file.encoding プロパティに設定されており,
    System.getProperty( "file.encoding" ) の返値のString型文字列定数として知ることができる。

 ・コンストラクタの実引数に最終出力先のファイルを指定する情報(例:ファイル名を表すString型文字列)を渡して,FileWriter
  型ストリームをオープンすると,すぐにファイルに書き込める状態になる。

ことである。そのため手軽にテキストデータをファイルへ書き込むことができる。しかし,書き込み
時に必ずデフォルトエンコーディングの文字に変換して書き込むため,特定の文字コードのテキスト
ファイルを作成しなければならない場合などには使用できない。

 FileWriterの使用例 を以下に示す。FileWriterのコンストラクタを呼び出してストリームをオープンする際,
  ・指定したファイルが存在していない場合は,新規に(内容が空の)ファイルを作成する。
  ・指定したファイルがすでに存在していた場合は,既存のファイルの内容は空にされる。
ことに注意しよう。

以下に,FileWriterを利用した簡単なデフォルトエンコーディングによるテキストファイル書き込みの例を示す。

ソースファイル:FileWriterTest.java
import java.io.*;

class FileWriterTest {
    
    public static void main ( String [ ] args ) throws IOException {
        
        FileWriter fw = null;
           
        try {
            // 同じ名前のファイルが同じ場所にすでに存在していたら,既存の内容は消去される。
            fw = new FileWriter( "FileWriterTest.txt" );
            fw.write( "こんにちは。" ); // デフォルトエンコーディングで文字列を書き込む。
        }
        catch( IOException ioe ) {
            System.out.println( ioe );
        }
        finally {
            if( fw != null ) fw.close( );
        }
        
    }

}

繰り返し強調するが,FileWriterを利用したファイル書き込みでは,デフォルトエンコーディングでしか書き込めない。

本授業では,テキストファイルを書き込む方法としては,この後
 ■4.4. 『テキストファイルの書き込み例』
で解説する,様々な文字エンコーディングのファイルを作成・書き込める方法を推奨したい。



■4.3. 『文字出力用のラッパストリーム』

 文字出力用のラッパストリームは,
 ・バイトデータ出力ストリームの OutputStream をラップするもの
 ・
Writer をラップするものの2種類がある。

前者は基本ストリームとして紹介したので,後者を見ていこう。

■ BufferedWriter

 BufferedWriter は,ライタをラップすることでバッファ機能を持たせたラッパライタである。
主なコンストラクタとメソッドをTable 5-(6)および下表に示す。行末記号を書き込むメソッド newLine( ) がある。

BufferedWriter のコンストラクタ

 BufferedWriter( Writer out )

 BufferedWriter( Writer out, int sz )


Writer型ストリームオブジェクト outを出力先としてラップするようにするコンストラクタ。バッファサイズを第2引数szで指定することもできる。


BufferedWriter のメソッド
※Writerから継承したメソッド(write等)の他に,以下の特徴的なメソッドを持つ。


 void newLine( )



行末記号を出力先のストリームに出力する。

※行末記号は,line.separatorプロパティで定義されており,
  System.lineSeparator( ) か System.getProperty( "line.separator" )
 の返値で,String型の文字列定数として得ることができる。
※入出力エラーが発生した場合は,IOException例外を投げる。


■ PrintWriter

 PrintWriter は,PrintStream (System.outのデータ型)の改良版として導入された。出力先として
OutputStream または Writer をラップして,データをフォーマットつきで出力する機能を持つ
出力系ストリームである。Table 5-(7) に,コンストラクタと独自メソッドを示す。
  このストリームは,Writer だけでなく, OutputStream もラップすることができる。
OutputStream をラップする場合は,まず OutputStream をラップする OutputStreamWriter
を生成して,それをさらにラップするようになる
。このとき,文字を書き込むと,Javaの
内部文字コード(UTF-16 ビッグエンディアン)の文字データをデフォルトのエンコーディングへ
変換してから出力することになる。print( ) メソッドや println( ) メソッドで,例外を投げる事
が無い等,気軽にオブジェクトや基本データの値を出力するのに使用できる。


■4.4. 『テキストファイルの書き込み例』

 では,実際にテキストファイルに文字データを書き込んでみよう。テキストデータ入力の時に
ストリームを使用した入出力プログラムの手順にはパターンがあることを紹介した(下図再掲) 。
テキスト出力ストリームで出力する際も,この手順パターンにしたがってプログラムを書けば良い。



まず,Eclipseで Javaプロジェクトを新規作成する。作成したJavaプロジェクトに以下のソース
プログラム TextWriting.java を作成して実行してみよ。

ソースプログラム:TextWriting.java


 実行すると,プロジェクトフォルダ直下に「WrintgTest.txt」というテキストファイルが生成
されるはずである。Eclipseのパッケージエクスプローラーウインドウ上でも「WritingText.txt」
がプロジェクトフォルダ直下に確認できるはずである(下図)。次の何れかで,「WritingText.txt」
に書き込まれた内容が確認できる。

 ・このまま,Eclipseのパッケージエクスプローラーウインドウ上で「WritingText.txt」をダブルクリックすると
  Eclipseのテキストエディタでファイル内容が表示される。
 ・Eclipseのパッケージエクスプローラーウインドウ上で「WritingText.txt」を右クリックして,表示されたメニュー
  で「Windows エクスプローラー」を選ぶと,Windowsで「WritingText.txt」が表示されるので,メモ帳などのテキスト
  エディタで開く。

書き込む内容を変えて何回か書き込んでみよ(実行するたびにテキストファイル「WrintgTest.txt」
の内容は上書きされる)。

上例のプログラムを以下に解説する。

※まず,入出力ストリームの機能はほとんどエラーが起こったときに例外を投げるので,例外の枠組みを使って記述することに注意しよう。
①:java.io パッケージをインポートする。
②:mainメソッドに IOException例外を投げることを明示する例外指定を書いている。理由は後述する。
③:tryブロックの前で,使用するストリームオブジェクトを参照するための変数をnull を初期値として宣言しておく。この例では,
   FileOutputStream, OutputStreamWriter, BufferedWriter
  を使用する。
④:まず,最終出力先であるテキストファイルと接続するFileOutputStream型の基本ストリームをオープンする。出力先の情報として
  コンストラクタへの実引数としてテキストファイル名("WritingText.txt")を渡していることに注意。以下の様な状態になる。


⑤:出力として④でオープンしたFileOutputStream型ストリームへの参照値 fos と,文字エンコーディングとしてUTF-8を表す"UTF8"を,
  コンストラクタの実引数として渡し,OutputStreamWriter型ストリームをオープンしている。以下の様な状態になる。


⑥:出力先として⑤でオープンしたOutputStreamWriter型ストリームへの参照値 osw をコンストラクタの実引数として指定しBufferedWriter型
  ストリームをオープンする。以下の様な状態になる。これで,テキストファイルから文字データを行単位で読み込む準備が整った。


⑦:BufferedWriter型ストリーム bw の writeメソッドを使って文字列を書き込み,その後,行末記号のみを出力するために実引数無しで
  newLineメソッドを呼び出している 。
⑧:出力ストリームに特徴的なのが,flushメソッドで,確実に出力先に書き込み内容を一気に書き込む。ただ flushメソッドは実行に時間が
  かかるので,頻繁に呼び出すのは避け,ある程度多くの書き込みをしたタイミングで呼び出すと良い

⑨〜⑩:発生する可能性のある例外をキャッチしてエラーへの対処を行う。ここでは単にキャッチした例外オブジェクトを表示しているだけ。
⑪:tryブロックの中の処理で例外が発生する・しないに関係無く,finallyブロック内で使い終わったすべてのストリームを入力源から遠い
  ラッパストリームから順にクローズしていく。 最終出力先から一番遠いラッパストリーム BufferedWriter型ストリーム bw のcloseメソッド
  だけ呼べばいいと思うかもしれない(closeメソッドは出力先ストリームのcloseメソッドを連鎖的に呼び出すため)。しかし,もし
  BufferedWriter型オブジェクトをオープンしようとすところでエラーが起こった場合,
    if( bw != null ) bw.close( );
  だけでは, FileOutputStream型ストリームと OutputStreamWriter型ストリームはクローズされないことになってしまう。
※③で,tryブロックの前でストリーム参照用の変数を宣言したのは,finallyブロックでこれらの変数を使用するため。
※②で,mainメソッドに IOException例外の例外指定をしたのは,finallyブロック内で呼び出す close メソッドが IOException例外を投げる
 可能性があるため。このままでは,finallyブロック内で呼び出す close メソッドで例外が発生した場合は,mainメソッドすなわちプログラム
 自体が強制終了するため,それを防ぎたい場合は,try-catch-finallyブロックをさらに tryブロックで囲み,この外部のtryブロックで
 IOException例外をキャッチするようにしよう。



次に進む