InputStream/OutputStream型

OutputStream型

データを書き出すための Stream であり,使い方は Writer とほぼ同じです. 例えば,以下のサンプルプログラムの writeWithWriterWriter型で示した典型的なWriterの使い方です. このwriteWithWriterと対比して,writeWithOutputStreamを確認してください.

void writeWithWriter(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();
}

void writeWithOutputStream(File file, String message) throws IOException{
    BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); // (3)
    // 上記の(3)の処理を区別して書くと次のような処理になる.
    //     FileOutputStream fout = new FileOutputStream(file);
    //     BufferedOutputStream out = new BufferedOutputStream(fout);
    out.write(message.getBytes());
    out.close();
}

writeWithWriterwriteWithOutputStreamの違いは,メソッド内で利用している型のみで,構造がほぼ同じであることに注意してください. writeWithOutputStream では,ファイルを開き,その型(ここではFileOutputStream)を別の型(BufferedOutputStream)でラップしています. そして,BufferedOutputStreammessage.getBytes() の返り値をwriteメソッドで書き出しています. message.getBytes()は名前から想像できるように,文字列(String型)のバイト配列を返します. このことから,OutputStreamは文字データ(Character型)ではなく,バイナリデータ(Byte型)を扱うことに注意しましょう.

加えて,利用している型は異なりますが,名前が似ている型があることにも注意しましょう. 例えば,writeWithWriterメソッドでは,中で FileWriterを利用していますが, writeWithOutputStreamメソッドでは,FileWriterではなく,FileOutputStreamを利用しています.

OutputStream型色々

OutputStream型もWriter型と同様に多くの型が存在します. ファイルに書き出すときは,FileOutputStreamを使いますが,典型的には OutputStream の機能のみ(writeメソッドにInetger型を渡す)を使うことが多いでしょう.

InputStream型

InputStreamはデータを読み込むための Stream であり,バイナリデータを読み込みます. 使い方は Reader とほぼ同じで,Stream を開き,そこからデータを読み取ります. readメソッドで1バイトのデータを読み込みます.この時,Integer型でデータが返されます. 返り値となるデータが-1の場合,これ以上,読み取るデータがないことを表します.

InputStream型色々

InputStream型もReader型と同様に多くの型が存在します. ファイルからデータを読み出すときは,FileInputStreamを使います. また,読み込むときにバッファリングしたい場合は,BufferedInputStreamを使うことが多いでしょう. それでも,本講義で扱うプログラムでは,引数のないreadメソッドを呼び出し,1バイト単位で読み込む方法で十分でしょう.

例題4 InputStreamを利用したCat

InputStream はバイナリデータですので,行の概念がありません.改行コードも文字も同列に扱われるためです. それでも,例題1のようなプログラムを作成可能です. 1バイトずつ読みこみ,1バイトずつ書き出せば良いのです. 以下の例をコンパイル,実行してください.

import java.io.*;
public class CatByStream{
    void cat(String file) throws IOException{
        System.out.printf("========== %s ==========%n", file);
        FileInputStream in = new FileInputStream(file);
        this.printFileContent(in);
        in.close();
    }
    void printFileContent(FileInputStream in) throws IOException{
        Integer data;
        while((data = in.read()) != -1){
            System.out.write(data);
        }
    }
    // run や main メソッドは省略.
}

このプログラムのprintFileContentメソッドを見てみましょう. FileInputStream型の変数inに対してreadメソッドを呼び出し,1バイトのデータを受け取っています. 読み取れるデータがない場合は -1 が帰ります.

そして,受け取ったデータを変数dataに代入し,System.out.writeメソッドが標準出力に書き出しています. このように1バイト単位で読み出し,書き出しすることでも,Catコマンドを実現可能です.

プログラム全体

例題5 単純なファイルコピー2

それでは,例題3 単純なファイルコピーInputStream/OutputStreamを使って書き直して見ましょう. クラス名は SimpleCopier2 としてください.

解答例

例題6 pnm 画像の生成

pnm 画像とは,pbm, pgm, ppmの総称で非常に単純な画像フォーマットです. それぞれ,portable binary map, portable gray map, portable pix mapの略です. ここで,ppm 画像を作成します. 3バイトで1画像を表します. 画素とは画像の1ピクセルのことです.

ppm画像は,次のようなフォーマットです.各行は1バイトの改行(\n)で区切られます. このうち,WIDTHは横幅を表す整数値,HEIGHTは高さを表す整数値で,画像データはバイナリデータで画素の左上から始まり左に向かって出力されます. 一番右端のデータが書かれれば,一段下の行の左端からのデータが書き出されます. そして,1画素あたり,R(赤成分),G(緑成分),B(青成分)の順で書き出され,これがWIDTH*HEIGHT*3バイト続きます.

P6
WIDTH HEIGHT
255
画像データ

このようなppm画像で,下に示すようなグラデーション表示をしてみましょう. 256×256 の大きさで,左上が黒(RGBがそれぞれ0),左下が赤,右上が青,右下がマゼンタ(RGBでRとB成分が255,G成分が0)になるように画像を作成してみましょう. なお,ppm 画像は,macOSのプレビューで確認できます.

では,GradiationGeneratorというクラス名で作成してみましょう. 生成した画像を gradiation.ppm というファイルに出力しましょう.

生成するグラデーション画像

// import文は省略
public class GradiationGenerator {
    void run() throws IOException{
        OutputStream out = // gradiation.ppm に出力する OutputStream を構築する.
        this.writeHeader(out);
        this.writeBody(out);
        out.close();
    }

    void writeBody(OutputStream out) throws IOException{
        // 各画素をR, G, B それぞれ1バイトで出力する.
    }

    void writeHeader(OutputStream out) throws IOException{
        out.write('P');  // ヘッダ部分は文字で表される.
        out.write('6');
        out.write('\n'); // 環境が変わっても \n を出力する必要がある.
        out.write('2');  // 横幅
        out.write('5');
        out.write('6');
        out.write(' ');
        out.write('2');  // 高さ
        out.write('5');
        out.write('6');
        out.write('\n');
        out.write('2');  // 1画素の1色成分の最大値
        out.write('5');
        out.write('5');
        out.write('\n');
    }
    // mainメソッドは省略.
}
解答例