Java雑記

(作成:2005/09)

入出力

なかなか面白い事を言うサイトを見付けて印象に残ったこと(アドレス忘却。。。失礼)。使い方も様々ではあるけれど、xxxxStream系のクラスはフィルタのように考えれば良いらしい。そのサイトでは水道局で表現してたけど。どこだったかなぁ。

テキストデータ簡単出力

よく使う System.out.println() 関数みたくテキストデータを保存するには、 java.io.PrintStream てのを使うと良い。

import java.io.*;

public class Test {
    static PrintStream out = null;

    public static void main(String args[]) {
        String[] data =
            {"arch", "bash", "cat", "chgrp", "chmod",
             "chown", "cp", "cpio", "csh", "date"};

        try {
            out = new PrintStream(
                  new FileOutputStream("hoge.txt.gz"));

            for(int i = 0;i < data.length;i++) {
                out.println(data[i]);
            }

            out.close();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
    }
}

バッファを用いて一度に書き込む

上記プログラムの振舞いをjdbで見ながら実行してみると、こんな事が分かる。

$ javac -g Test.java
$ jdb Test
jdb の初期化中です...
> stop at Test:15
ブレークポイント Test:15 を保留しています。
クラスがロードされた後に設定されます。
> run
Testを実行します
uncaught java.lang.Throwable を設定しました
保留した uncaught java.lang.Throwable を設定しました
> 
VM が起動しました: 保留した ブレークポイント Test:15 を設定しました

ブレークポイントのヒット: "スレッド=main", Test.main(), line=15 bci=79
15                      for(int i = 0;i < data.length;i++) {

main[1]

この時の hoge.txt.gz

 -rw-rw-r--  1 riyo local    0 2005-09-21 15:55 hoge.txt

こんなん。更に実行してみる。

main[1] next
> 
ステップ完了: "スレッド=main", Test.main(), line=16 bci=87
16                              out.println(data[i]);

main[1] next
> 
ステップ完了: "スレッド=main", Test.main(), line=15 bci=96
15                      for(int i = 0;i < data.length;i++) {

main[1]

ここまで実行して

 -rw-rw-r--  1 riyo local    5 2005-09-21 15:59 hoge.txt

うん、増えてる。リアルタイムにファイル出力してるのが判る。
ただ、場合に依りけりだけど、普通は無駄なアクセスが増えるんであんまし嬉しかぁない。つー事でバッファリングしながら纏めて一気にファイル出力する為の方法。

上記例を改変するならこんな感じに途中に噛ませるだけ。最後にバッファを残さないよう flush() すること。

...
         try {
            out = new PrintStream(
                  new BufferedOutputStream(
                  new FileOutputStream("hoge.txt"));

            for(int i = 0;i < data.length;i++) {
                out.println(data[i]);
            }

            out.flush();
            out.close();
...

Gzipファイル出力

ログなんかをテキストファイルにすると、どうしてもばかでかいファイルになりがち。そんな時にGzip圧縮掛けとくとそれなりに小さいファイルになって便利。
java.io.PrintStream のファイル出力を修正すると、こんな感じになる。

import java.util.zip.*;
import java.io.*;
...
            out = new PrintStream(
                  new GZIPOutputStream(
                  new FileOutputStream("hoge.txt.gz")));

この例だとむしろGzip圧縮しない方が容量少ないけれど、物凄い大量のテキストデータを扱うような場合には結構重宝する。
なおこの場合、 jdb で振舞いを見たところバッファリングされてるみたい。圧縮する関係上かな。

バイナリファイル入出力

数値データなんかの場合、テキストにしちゃうとGzip圧縮したとしても無駄が多い。そりゃ1文字につき1Byte使ってんもんね。

例えばJavaの int 最大値 2147483647 ならこれで10Byte。エディタなんかでパースすんなら空白みたいなデリミタが必要だから、少なくとも更に+1Byte。
でもint型のまま保存出来るなら、大きさ固定なんでデリミタ要らずで4Byteのみで済んじゃう。約1/3倍。
これが積もり積もれば凄い差になる。ウチの手掛けてる研究の実験結果なんかは数多くやってナンボなもんだから、そりゃバイナリにして少しでも容量減らした方が良いやね。てなわけで。

バイナリファイルを作成する際は、必ずそのファイル構造仕様を決めてから取り掛かること。作りっ放しじゃ意味無いかんね。

import java.io.*;

public class Test {
    static DataOutputStream out = null;

    public static void main(String args[]) {
        try {
            out = new DataOutputStream(new FileOutputStream("bin.dat"));
            double[] ddata = {0.10375824386833987, 2.3394785219320156, Double.MAX_VALUE};
            int[] idata = {1, 14, Integer.MAX_VALUE};

            out.writeChars("DAT");
            for(int i = 0;i < 3;i++) {
                out.writeDouble(ddata[i]);
                out.writeInt(idata[i]);
            }

            out.close();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
    }
}

これは例なんで仕様とかは適当だけど、バイナリデータには以下のような形式で入る。

Header Body
ASCII(2Byte) * 3 ( Double(8Byte)+ Integer(4Byte)) * 3

ちょっと悲しいのは、Javaのバイナリ編集の場合は文字をUTF-16で扱うところ。普通のEUCテキストよか容量食っちゃうのよね。これ何とかならないかなぁ。

さておき。バイナリなんで、当然 cat しても訳分からん表示がされるだけ。そんなわけで、これを出力するプログラムも組んでみる。

import java.lang.*;
import java.io.*;

public class Test2 {
    static DataInputStream in = null;

    public static void main(String args[]) {
        try {
            in = new DataInputStream(new FileInputStream("bin.dat"));

            char[] dataType = new char[3];
            for(int i = 0;i < 3;i++) {
                dataType[i] = in.readChar();
            }
            System.out.println(String.valueOf(dataType));
            for(int i = 0;i < 3;i++) {
                System.out.println(Double.toString(in.readDouble()));
                System.out.println(Integer.toString(in.readInt()));
            }

            in.close();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
    }
}

実行結果はこんな感じ。

$ java Test2
DAT
0.10375824386833987
1
2.3394785219320156
14
1.7976931348623157E308
2147483647

バイナリデータの仕様や各プログラミング言語のデータサイズなんかを理解してれば、このデータを他のプログラムで閲覧する事も可能。例えばウチはデータ整理をPerlでやってるんで、拙サイト「Perl雑記 - Javaで作成したバイナリデータの読込」で詳解しているようなスクリプトでデータを閲覧出来る。

Perl雑記
(作成:2005/10) 定数・変数・値 多次元配列の初期化 こんな感じに、参照渡しをしてやるのが楽ちん。勿論 @hoge0 、 @...