ファイル入出力

Java アプリケーションについて,ファイルへの出力,ファイルからの入力を勉強します。 Webブラウザの中で動くアプレットについては,セキュリティの観点から,ファイルへの入出力は許されていません。

ファイルの入出力(バッファなし)

次のプログラムは,foo.txt というファイルを読んで,内容をそのまま bar.txt というファイルに書き出します。

import java.io.*;
class Test {
    public static void main(String[] args) {
        try {
            FileInputStream in
                = new FileInputStream("foo.txt");  // 入力ファイルを開く
            FileOutputStream out
                = new FileOutputStream("bar.txt"); // 出力ファイルを開く
            int c;
            while ((c = in.read()) != -1)          // 入力
                out.write(c);                      // 出力
            in.close();             // 入力ファイルを閉じる
            out.close();            // 出力ファイルを閉じる
        } catch (IOException e) {   // 入出力エラーをつかまえる
            System.err.println(e);  // エラーメッセージ出力
            System.exit(1);         // 終了コード 1 で終了する
        }
    }
}

もし foo.txt が存在しないと,15行目の System.err.println(e);

java.io.FileNotFoundException: foo.txt (そのようなファイルやディレクトリはありません)

と出力して止まってしまいます。 また,もし bar.txt があると,それに上書きしてしまいますので,気をつけてください。

実際には foo.txtbar.txt のような具体的なファイル名をプログラムの中に書き込むと一般性がなくなります。 コマンドラインでファイル名を指定できるようにするのがいいでしょう。 コマンドラインとは

java Test

のようにコマンドを打ち込んだ行のことです。このように打ち込む代わりに

java Test abc.txt def.txt

と打ち込めば abc.txt の内容を def.txt にコピーするようにしてみましょう。それには次のようにします。

import java.io.*;
class Test {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.err.println("使い方: java Test 入力ファイル 出力ファイル");
            System.exit(0);
        }
        try {
            ....(以下同じ)

main(String[] args) は,じつはコマンドライン引数を入れるための文字列の配列だったのです。 たとえば

java Test abc.txt def.txt

と打ち込んだとき,abc.txtdef.txt という文字列のことをコマンドライン引数といいます。 この場合,abc.txtargs[0] に,def.txtargs[1] に代入されます。 また,引数の数(この場合は2)が args.length に代入されます。 もしコマンドライン引数の個数が2以外なら,標準エラー出力(要するに画面)に

使い方: java Test 入力ファイル 出力ファイル

と出力し,終了します。終了は

    System.exit(0);

のようにします。この 0 は,プログラムの戻り値といって,調べようと思えば調べることのできる値です(通常は無視します)。 慣習的に,正常終了時は 0 を返し,エラーのときは 0 以外の適当なエラー番号を返すことになっています。

ファイルの入出力(バッファあり)

上のプログラムは,ディスクから1文字(1バイト)ずつ読み書きしていますので,あまり能率的ではありません。 上の

    FileInputStream  in  = new FileInputStream (args[0]);
    FileOutputStream out = new FileOutputStream(args[1]);

のところを

    BufferedInputStream in
        = new BufferedInputStream (new FileInputStream (args[0]));
    BufferedOutputStream out
        = new BufferedOutputStream(new FileOutputStream(args[1]));

とするだけで,ずいぶんプログラムの実行速度が上がります。 これはバッファリングの効果です。

バッファ(buffer)とは一般に「緩衝(かんしょう)装置」つまり二つのものの間にはさまって衝撃を和らげるもののことです。

ファイルからの入力の場合は,1バイトずつディスクから読むと時間がかかるので,一度にたとえば2048バイト読み込んでメモリの一部(バッファ)に保存しておき,2度目からは実際に読むのではなくメモリからコピーしてくることを意味します。

ファイルへの出力も同様で,最初は実際に書き込まずバッファに保存しておき,たとえば512バイトになると一気に書き出します。

バッファを使うことをバッファリング(buffering)するともいいます。

バッファリングしているときは,最後の文字を書き出したつもりでも,実際には端数分がメモリに残っています。 この端数分は,出力ファイルをクローズした(閉じた)時点でディスクに書き込まれます。 つまり,output.close() を実行した時点で書き込まれます。 クローズを忘れると,ファイルが全部書きおわらないうちに終了してしまうことがありますので,必ずオープンしたらクローズする習慣をつけましょう。

テキストファイルの入出力

上のプログラム例はバイナリファイルでもテキストファイルでも使えますが,特にテキストファイルの場合は,バイト単位で読むより,文字単位,あるいは行単位で読む方が便利なことがあります。 とりあえずは上のプログラムの

    FileInputStream  in  = new FileInputStream (args[0]);
    FileOutputStream out = new FileOutputStream(args[1]);

のところを

    BufferedReader in  = new BufferedReader(new FileReader(args[0]));
    BufferedWriter out = new BufferedWriter(new FileWriter(args[1]));

のように直すと,c に入るのは文字単位になります。 日本語なら,2バイトずつ読んで,Unicodeに変換した結果が c に入ります。 Unicodeですからシステムに依存しませんが,行末はシステムに依存します(Windowsでは2文字,UNIXでは1文字として扱われます)。

文字ごとではなく行ごとに読むには,

            int c;
            while ((c = in.read()) != -1)          // 入力
                out.write(c);                      // 出力

の部分を

            String s;
            while ((s = in.readLine()) != null) {  // 入力
                out.write(s);                      // 出力
                out.newLine();                     // 改行
            }

にします。 行はUnicodeを並べた文字列に変換されますが,出力時には処理系の文字コードに戻されます。 入力行に改行は含まれませんので,行を出力した後に,改行を出力する newLine() メソッドを使います。 この方法なら,システムごとに改行コードの違いを考える必要もありません。

readLine() は,ファイルの終わりまで読み尽くした後では,null という特別な値を返します。 これが返ってきたら,繰返しを終了します。

エンコーディング

日本語の処理を複雑にしている理由の一つに,エンコーディング(文字コード)の問題があります。 WindowsではシフトJIS(SJIS)というエンコーディングを使いますが,UNIXではEUC(EUC-JP)が多く,電子メールなどはいわゆるJIS(ISO-2022-JP)エンコーディングを使います。

Javaインタープリタはその環境の標準的なエンコーディングを自動的に判断して使ってくれます。 エンコーディングを調べるには,次のようなプログラムを走らせます。

class EncTest {
    public static void main(String[] args) {
        System.out.println(System.getProperty("file.encoding"));
    }
}

標準的なエンコーディングを使用しない場合は,次の例のようにエンコーディングを指定します。

import java.io.*;
class EUCtoSJIS {
    public static void main(String[] args) throws IOException {
        BufferedReader in
            = new BufferedReader(
                  new InputStreamReader(
                      new FileInputStream(args[0]),
                      "EUC-JP"));
        BufferedWriter out
            = new BufferedWriter(
                  new OutputStreamWriter(
                      new FileOutputStream(args[1]),
                      "SJIS"));
        String s;
        while ((s = in.readLine()) != null) {
            out.write(s);
            out.newLine();
        }
        in.close();
        out.close();
    }
}

上の例はEUCエンコーディングで読んでシフトJISで書き出す場合です。 日本語のエンコーディングは SJISEUC-JPISO-2022-JP のほか自動判定 JISAutoDetect があります。

欧米でよく使われるLatin-1エンコーディングは ISO-8859-1 と指定します。 特に日本語として処理する必要がなければ,これを指定して読むのが安全です。


奥村晴彦

Last modified: 2005-09-22 15:44:04