連載
» 2007年10月24日 00時00分 公開

プログラマーの常識をJavaで身につける(8):意外と知らないファイル圧縮技術の常識 (3/4)

[伊賀敏樹,株式会社 NTTデータ ビジネスブレインズ]

java.util.zipパッケージでzipファイルを操作

zipファイル形式の概要

 zip形式には、ファイル圧縮機能とアーカイブ機能があります。アーカイブ機能がある分、gzip形式よりも複雑です。このため、zip形式を扱うJava APIも、より複雑な構造になっています。

 zipファイルを標準APIで利用できるのはとても便利ですが、残念なことに、ここで注意しなければならない点をお知らせしなくてはなりません。

zipファイル名の文字コードによる悲劇

 java.util.zipパッケージのzipファイル入出力は、zipファイルをアーカイブする際にファイル名としてUTF-8文字エンコーディングを利用しています。これはさまざまな国と地域で利用されるJava言語にとって自然な判断なのでしょう。ところが、世間一般に出回っているzipファイルを扱うためのユーティリティプログラム(「アーカイバ」などと呼ばれる)はUTF-8文字エンコーディングによるファイル名に対応していないものがほとんどなのです。

 Windows用のものはShift_JIS文字エンコーディング(正確には、Windows-31J文字エンコーディング)でファイル名が格納されていると仮定したものが多いのです(Windows XP標準で提供されているzip形式関連機能も同様です)。

 このサンプルプログラムでISO/IEC 646(通称、ASCIIコード)の文字以外の文字を利用したファイル名を含んだzipファイルの入出力を行うと、トラブルに遭遇する場合がありますので、ご注意ください(ISO/IEC 646(通称、ASCIIコード)については連載第6回「‘愛’で学ぶ文字コードと文字化けの常識」の「文字化けには問題がこんなにたくさんある!」を参照してください)。

zipファイルを作成するサンプル

 zipファイルを作成するためには、java.util.zip.ZipOutputStreamクラスを利用します。java.util.zip.GZIPOutputStreamと異なり、java.util.zip.ZipEntryというアーカイブに含まれるファイルやディレクトリを表すクラスを利用しながら、ファイル圧縮およびアーカイブを行います。java.util.zip.ZipOutputStreamにより圧縮されて連結先の出力ストリーム(この例では、java.io.BufferedOutputStream)に書き込まれていきます。

zipファイルを作成するサンプル
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/** zipファイルを作成するサンプル */
public class CreateZipSample {
    public static void main(String[] args) {
        try {
            final File file = new File("zipsample.zip");
            System.out.println("zipファイル作成: 開始: " +
                file.getAbsolutePath());
            new CreateZipSample().process(file);
            System.out.println("zipファイル作成: 終了");
        } catch (IOException e) {
            System.out.println("zipファイル作成中に"+
                "例外が発生しました。処理中断します:" +
                e.toString());
        }
    }
    /**
     * zipファイルを作成します
     *
     * @param file 出力するzipファイル。
     * @throws IOException 入出力例外が発生した場合。
     */
    public void process(final File file) throws IOException {
        final ZipOutputStream zipOutStream = new ZipOutputStream(
            new BufferedOutputStream(new FileOutputStream(file)));
        try {
        {
            /* ディレクトリの作成 */
            final ZipEntry entry = new ZipEntry("ABC/");
            entry.setMethod(ZipEntry.STORED);
            entry.setSize(0);
            entry.setCrc(0);
            zipOutStream.putNextEntry(entry);
            zipOutStream.closeEntry();
        }
        {
            final ZipEntry entry = new ZipEntry("ABC/ABC.txt");
            zipOutStream.putNextEntry(entry);
            /* ファイルデータの書き込み */
            zipOutStream.write(
                new String("あいうえお").getBytes());
            zipOutStream.closeEntry();
        }
        {
            final ZipEntry entry = new ZipEntry("ABC/DEF.txt");
            zipOutStream.putNextEntry(entry);
            zipOutStream.write(
                 new String("かきくけこ").getBytes());
            zipOutStream.closeEntry();
        }
        {
            final ZipEntry entry = new ZipEntry("GHI.txt");
            zipOutStream.putNextEntry(entry);
            zipOutStream.write(
                 new String("さしすせそ").getBytes());
            zipOutStream.closeEntry();
        }
            zipOutStream.finish();
        } finally {
            zipOutStream.close();
        }
    }
}

実行結果(コンソール出力)

zipファイル作成: 開始: C:\Documents and Settings\iga\workspace\aaa\zipsample.zip

zipファイル作成: 終了


 このプログラムが作成したzipファイルを[エクスプローラ]で表示すると、下記のようになります。

 なお、Java APIを利用してディレクトリを作成すると、下記の「ABC」のように、ディレクトリ実体と余分なエントリの2つが作成されます。これは現状のJava APIの仕様上、仕方がないようです(Javaツールのjarコマンドにおいても同様の現象が発生します)。

図13 作成されたzipファイル 図13 作成されたzipファイル

zipファイルを読み込むサンプル

 次に、作成したzipファイルを展開(解凍)しながら読み込むサンプルプログラムを見ていきましょう。

 なお、このサンプルも、直前で示したCreateZipSampleが作成したzipファイルを読み込むという仮定の下に作られています。具体的には、ファイル名はUTF-8文字エンコーディング、読み込まれるデータは少量のテキストファイルであり、かつ同じデフォルト文字エンコーディング環境で動作しているものと仮定しています。

ZIPファイルを読み込むサンプル
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/** ZIPファイルを読み込むサンプル */
public class ReadZipSample {
    public static void main(final String[] args)
    throws IOException {
        try {
            final File file = new File("zipsample.zip");
            System.out.println("zipファイル読込: 開始: " +
                file.getAbsolutePath());
            new ReadZipSample().process(file);
            System.out.println("zipファイル読込: 終了");
        } catch (IOException e) {
            System.out.println("zipファイル読込中に"+
                "例外が発生しました。処理中断します:" +
                e.toString());
        }
    }
    /**
     * zipファイルを読み込みます
     *
     * @param file 入力するzipファイル
     * @throws IOException 入出力例外が発生した場合
     */
    public void process(final File file) throws IOException {
        final ZipInputStream zipInStream = new ZipInputStream(
            new BufferedInputStream(new FileInputStream(file)));
        try {
            for (;;) {
                final ZipEntry entry = zipInStream.getNextEntry();
                if (entry == null) break;
                if (entry.isDirectory()) {
                    System.out.println(" ディレクトリ名: [" +
                        entry.getName() + "]");
                } else {
                    System.out.println(" ファイル名: [" +
                        entry.getName() + "]");
                    /* ファイルデータの読み込み */
                    final ByteArrayOutputStream outStream
                        = new ByteArrayOutputStream();
                    for (;;) {
                        int iRead = zipInStream.read();
                        if (iRead < 0) break;
                        outStream.write(iRead);
                    }
                    outStream.flush();
                    outStream.close();
                    System.out.println(" 内容: ["
                        + new String(outStream.toByteArray()) +
                        "]");
                }
                zipInStream.closeEntry();
            }
        } finally {
            zipInStream.close();
        }
    }
}

 ここでは、java.util.zip.ZipInputStreamクラスを使用しています。連結先の入力ストリーム(ここでは、java.io.BufferedInputStream)からデータを読み込んで、java.util.zip.ZipEntryを取り出してアーカイブ前のディレクトリおよびファイルの情報を取り出し、zip形式で展開(解凍)しながら読み込んでいます。

実行結果(コンソール出力)

zipファイル読込: 開始: C:\Documents and Settings\iga\workspace\aaa\zipsample.zip

ディレクトリ名: [ABC/]

ファイル名: [ABC/ABC.txt]

内容: [あいうえお]

ファイル名: [ABC/DEF.txt]

内容: [かきくけこ]

ファイル名: [GHI.txt]

内容: [さしすせそ]

zipファイル読込: 終了


 いよいよ次ページでは、Javaに欠かせないjar形式を見ていきます。

java.util.jarパッケージでjarファイルを操作

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。