本ページはプロモーションが含まれています

Java入門

【Java】ZIP作成を3ステップで解説!基本から応用まで網羅

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

「Javaでファイルをまとめて圧縮したい」「生成した複数のレポートをZIP形式でダウンロードさせたい」

システム開発において、ファイル圧縮の要件が出てくる場面は少なくありません。私自身も、Webアプリケーション開発で帳票ファイルを一括ダウンロードさせる機能を作る際、JavaでのZIP作成方法を学びました。

しかし、当時はファイル名の文字化けや、大きなファイルを扱う際のメモリ不足など、数々のエラーに悩まされた経験があります。

この記事は、過去の私と同じように悩んでいる「JavaでのZIP作成について知りたい方」に向けて書いています。

Javaの標準機能を使ったZIP作成の基本から、実務で役立つ応用テクニックまで体系的に理解できます。サンプルコードを豊富に交えながら、初心者にも分かりやすく解説していきます。

この記事でわかること

  • JavaでZIPファイルを作成する3ステップの基本手順
  • 複数ファイル・ディレクトリのZIP化方法
  • 文字化け対策やパスワード付きZIPなどの応用テクニック
  • ZIP作成でよくあるエラーの原因と対処法

JavaでZIPファイルを作成する基本を解説

最初に、JavaでZIPファイルを作成するための基本的な知識をおさえておきましょう。ZIP形式そのものへの理解と、Javaが提供する標準APIについて解説します。

ZIPとは?圧縮の仕組みとメリット

ZIPとは、複数のファイルを1つのファイルにまとめる「アーカイブ」と、ファイルサイズを小さくする「圧縮」を同時に行えるファイル形式です。

  • アーカイブ機能: 関連するファイル群を1つにまとめることで、管理や送受信が容易になります。
  • 圧縮機能: ファイルの内容を保持したままデータ量を減らせるため、ストレージ容量の節約や、ネットワーク経由での転送時間短縮につながります。

ZIPの手軽さと利便性から、ソフトウェアの配布やデータのバックアップなど、幅広い用途で利用されているのです。

JavaでZIPを扱う標準API「java.util.zip」とは

Javaには、ZIPファイルを操作するための標準APIがjava.util.zipパッケージとして提供されています。外部ライブラリを追加しなくても、基本的なZIP作成や展開が可能です。

特に重要なクラスは以下の3つです。

クラス名役割
ZipOutputStreamZIPファイルへデータを書き込むためのクラス
ZipInputStreamZIPファイルからデータを読み込むためのクラス
ZipEntryZIPファイル内の個々のファイルやディレクトリを表すクラス

この記事では、ZIP作成に焦点を当て、主にZipOutputStreamZipEntryの使い方を詳しく見ていきます。

ZIP作成の基本構文(ZipOutputStreamの使い方)

JavaでのZIP作成は、以下の3ステップで行うのが基本の流れです。

  1. 出力先の指定: FileOutputStreamで、どこにZIPファイルを作成するかを指定します。
  2. ZIPストリームの作成: FileOutputStreamを使い、ZipOutputStreamのインスタンスを生成します。
  3. ファイルを追加: ZipEntryでZIP内に格納するファイル名を設定し、putNextEntry()で追加。ファイルの中身を書き込んだ後、closeEntry()で閉じます。

3ステップの流れを制御することで、単一ファイルからディレクトリまで、柔軟なZIP作成ができます。

サンプルコードで理解するZIP作成手順

具体的なサンプルコードを見ながら、JavaでのZIP作成手順を学びましょう。基本的なものから、少し複雑なものまで4つのパターンを紹介します。

単一ファイルをZIP化するサンプル

まずは最もシンプルな、1つのファイルをZIPファイルに圧縮するサンプルコードです。source.txtというファイルをarchive.zipという名前で圧縮します。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class SingleFileZip {
    public static void main(String[] args) {
        String sourceFilePath = "source.txt";
        String zipFilePath = "archive.zip";

        try (FileOutputStream fos = new FileOutputStream(zipFilePath);
             ZipOutputStream zos = new ZipOutputStream(fos);
             FileInputStream fis = new FileInputStream(sourceFilePath)) {

            // ZIPファイル内のエントリを作成
            ZipEntry zipEntry = new ZipEntry("source.txt");
            zos.putNextEntry(zipEntry);

            // ファイルを読み込み、ZIPファイルに書き込む
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) >= 0) {
                zos.write(buffer, 0, length);
            }

            zos.closeEntry();
            System.out.println("ZIPファイルの作成が完了しました。");

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

ZipEntryコンストラクタに渡す文字列が、ZIPファイル内に格納される際のファイル名になります。

複数ファイルをまとめてZIPにするサンプル

次に、複数のファイルを1つのZIPファイルにまとめる方法です。ファイル名のリストをループ処理で回し、1つずつZIPに追加していきます。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class MultiFileZip {
    public static void main(String[] args) {
        List<String> sourceFiles = Arrays.asList("file1.txt", "file2.log", "image.jpg");
        String zipFilePath = "multi_archive.zip";

        try (FileOutputStream fos = new FileOutputStream(zipFilePath);
             ZipOutputStream zos = new ZipOutputStream(fos)) {

            for (String sourceFile : sourceFiles) {
                try (FileInputStream fis = new FileInputStream(sourceFile)) {
                    ZipEntry zipEntry = new ZipEntry(sourceFile);
                    zos.putNextEntry(zipEntry);

                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = fis.read(buffer)) >= 0) {
                        zos.write(buffer, 0, length);
                    }
                    zos.closeEntry();
                }
            }
            System.out.println("複数ファイルのZIP化が完了しました。");

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

ループの中でFileInputStreamtry-with-resourcesで管理すると、安全にリソースを解放できます。

ディレクトリごとZIP化する処理のポイント

ディレクトリを丸ごとZIP化するには、ディレクトリ内のファイルやサブディレクトリを再帰的に探索する処理が必要です。

java.nio.fileパッケージのFiles.walk()を使うと、ディレクトリの階層構造を簡単にたどれます。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class DirectoryZip {
    public static void main(String[] args) throws IOException {
        String sourceDirPath = "my_directory";
        String zipFilePath = "directory_archive.zip";

        try (FileOutputStream fos = new FileOutputStream(zipFilePath);
             ZipOutputStream zos = new ZipOutputStream(fos)) {

            Path sourcePath = Paths.get(sourceDirPath);

            Files.walk(sourcePath).forEach(path -> {
                try {
                    // ZIPエントリ名を作成(相対パス)
                    String entryName = sourcePath.relativize(path).toString();
                    if (entryName.isEmpty()) {
                        return;
                    }

                    ZipEntry zipEntry = new ZipEntry(entryName);
                    zos.putNextEntry(zipEntry);

                    if (!Files.isDirectory(path)) {
                        try (FileInputStream fis = new FileInputStream(path.toFile())) {
                            byte[] buffer = new byte[1024];
                            int length;
                            while ((length = fis.read(buffer)) >= 0) {
                                zos.write(buffer, 0, length);
                            }
                        }
                    }
                    zos.closeEntry();
                } catch (IOException e) {
                    System.err.println("エラーが発生しました: " + e);
                }
            });
            System.out.println("ディレクトリのZIP化が完了しました。");
        }
    }
}

ポイントは、sourcePath.relativize(path)で圧縮元ディレクトリからの相対パスを計算し、ZIP内の階層構造を維持している点です。

文字化けを防ぐためのエンコーディング設定

Windows環境で作成したZIPファイルをMacやLinuxで展開した際、日本語のファイル名が文字化けすることがあります。文字化けは、OSごとに標準のファイル名エンコーディングが異なるために起こる現象です。

ZipOutputStreamのコンストラクタで文字コード(Charset)を指定することで、文字化けの問題を回避できます。

import java.nio.charset.Charset;
import java.util.zip.ZipOutputStream;
// ...

// Windows環境での互換性を高める場合
try (ZipOutputStream zos = new ZipOutputStream(fos, Charset.forName("MS932"))) {
    // ... 処理
}

// UTF-8を指定する場合
// import java.nio.charset.StandardCharsets;
try (ZipOutputStream zos = new ZipOutputStream(fos, StandardCharsets.UTF_8)) {
    // ... 処理
}

クロスプラットフォームで使うなら、UTF-8を指定するのがベストです。

ただし、Windowsの標準ZIP機能との互換性を優先するなら、MS932(Shift_JISの一種)が有効です。

実務で使える応用テクニック

基本のJavaでのZIP作成をマスターしたら、次は実務で求められる応用テクニックを身につけましょう。外部ライブラリの活用や、パフォーマンスを意識した作り方を紹介します。

パスワード付きZIPを作る方法(外部ライブラリ編)

標準APIではパスワード付きZIPは作れません。

Javaの標準APIjava.util.zipには、パスワード付きZIPファイルを作成する機能がありません。パスワード付きZIPを作るには、「zip4j」のような外部ライブラリを利用するのが一般的です。zip4jは非常に高機能で、パスワード設定(暗号化)も数行のコードで簡単に作れます。

Mavenでの依存関係追加 (pom.xml):

<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>2.11.5</version>
</dependency>

サンプルコード:

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.EncryptionMethod;

public class PasswordProtectedZip {
    public static void main(String[] args) {
        try {
            ZipParameters zipParameters = new ZipParameters();
            zipParameters.setEncryptFiles(true);
            zipParameters.setEncryptionMethod(EncryptionMethod.AES);

            ZipFile zipFile = new ZipFile("password_archive.zip", "your_password".toCharArray());
            zipFile.addFile("secret_document.txt", zipParameters);

            System.out.println("パスワード付きZIPが作成されました。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

zip4jを使えば複雑な暗号化処理を自前で作る必要がなく、手軽に安全なZIPファイルを作成できます。

既存ZIPへのファイル追加・更新

標準のZipOutputStreamは追記モードをサポートしていないため、既存のZIPファイルにファイルを追加するのは少し工夫が要ります。

Java 7から導入されたNIO.2(Java 7で追加されたファイル操作の新しいAPI)のFileSystem APIを利用すると、ZIPファイルをファイルシステムのように扱い、ファイルの追加や更新が可能です。

import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;

public class AddToZip {
    public static void main(String[] args) throws IOException {
        String zipFilePath = "archive.zip";
        String newFilePath = "new_file.txt";

        Map<String, String> env = new HashMap<>();
        env.put("create", "true");

        URI uri = URI.create("jar:file:/" + Paths.get(zipFilePath).toAbsolutePath());

        try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {
            Path pathInZip = zipfs.getPath("/" + newFilePath);
            Files.copy(Paths.get(newFilePath), pathInZip, StandardCopyOption.REPLACE_EXISTING);
            System.out.println("既存ZIPにファイルを追加しました。");
        }
    }
}

FileSystem APIを使う方法は、ZIP内の特定ファイルだけを更新したい場合にも有効です。

一時ファイルを使ったメモリ効率化

巨大ファイルにはストリーミング処理(データを一度に全部読み込まず、少しずつ処理する方式)が必須です。

数GBにもなるようなファイルを圧縮する場合、単純な作り方ではOutOfMemoryErrorが発生する危険性があります。

対策はストリーミング処理の徹底です。FileInputStreamから固定サイズのバッファ(例:byte[1024])で少しずつデータを読み込み、ZipOutputStreamに書き出します。

一度に全ファイルの内容をメモリ上に展開しないことで、ヒープ領域(Javaプログラムがデータを一時的に置くメモリ空間)の消費を最小限に抑え、安定した処理が可能になります。

try-with-resources構文で安全にストリームを閉じる

ファイルやネットワークを扱うストリーム処理では、リソースの解放(close()メソッドの呼び出し)が不可欠です。もしclose()を呼び忘れると、ファイルがロックされたままになったり、メモリリークの原因になったりします。

Java 7以降のtry-with-resources構文を使えば、tryブロック終了時にclose()が自動で呼ばれます。リソースの閉じ忘れを確実に防げます。

旧来の方法 (finallyブロック)

ZipOutputStream zos = null;
try {
    zos = new ZipOutputStream(...);
    // ... 処理
} catch (IOException e) {
    // ...
} finally {
    if (zos != null) {
        try {
            zos.close();
        } catch (IOException e) {
            // ...
        }
    }
}

try-with-resources構文

try (ZipOutputStream zos = new ZipOutputStream(...)) {
    // ... 処理
} catch (IOException e) {
    // ...
}

コードがシンプルになり、可読性も安全性も向上するため、ストリームを扱う際は積極的に活用しましょう。

ZIP作成でよくあるエラーと対処法

最後に、JavaでのZIP作成時によく遭遇するエラーとその対処法をまとめます。事前に知っておくことで、スムーズなデバッグにつながります。

「java.util.zip.ZipException」が出る原因

ZipExceptionは、ZIP処理におけるさまざまな問題で発生します。代表的な原因は以下の通りです。

原因

  • duplicate entry: <ファイル名>: 同じ名前のエントリ(ファイル)を1つのZIPに2回追加しようとすると発生します。追加するファイル名が重複していないか確認しましょう。
  • invalid entry size: ZipEntryに設定したサイズと、実際に書き込まれたデータのサイズが一致しない場合に起こります。あまり意識することはありませんが、setSize()を明示的に呼び出す場合は注意が必要です。
  • zip file is empty: 中身が空のZIPファイルを作成しようとした、あるいは破損したZIPファイルを読み込もうとした場合に発生します。

エラーメッセージをよく読み、何が原因で例外が起きているのかを特定することが解決への第一歩です。

パス指定ミス・ディレクトリ構造の崩れ対策

ディレクトリを圧縮する際に、意図しない階層構造になってしまうことがあります。原因は、ZIPエントリ名に絶対パスを指定してしまったり、パスの区切り文字がOSに依存していたりすることです。

対策として、ZIPエントリ名は必ず相対パスで指定することを徹底しましょう。

また、パスの区切り文字には、File.separatorを使うか、常にスラッシュ/を使用することで、OS間の互換性を保てます。

ファイルサイズが大きいときのメモリ対策

前述の通り、巨大なファイルを扱う際はメモリ使用量に注意が必要です。OutOfMemoryErrorが発生した場合は、以下の点を見直してください。

ポイント

  1. ストリーミング処理になっているか: 全データを一度にbyte配列へ読み込む作りは避けましょう。バッファを使って少しずつ書き込みます。
  2. JVMのヒープサイズ: JVM起動時にヒープ領域を増やすオプション(-Xmx)を指定します。例:-Xmx2048m(最大2,048MB)

根本的な解決策は1のストリーミング処理の見直しですが、一時的な対策として2のJVMオプション調整も覚えておきましょう。

まとめ|JavaでのZIP処理を自在に使いこなそう

JavaでのZIP作成の要点を振り返ります。

ZIP化の基本手順を整理

  • FileOutputStreamZipOutputStreamtry-with-resourcesで準備する。
  • ZipEntryでZIP内のファイル名やディレクトリ構造を定義する。
  • putNextEntry()でエントリを追加し、ファイルの中身をストリームで書き込む。
  • ファイルごとにcloseEntry()でエントリを閉じる。

実務で活きる応用ポイントのおさらい

  • 文字化け対策: ZipOutputStreamのコンストラクタでCharsetを指定する。
  • パスワード設定: 標準APIでは不可。「zip4j」などの外部ライブラリを利用する。
  • エラーハンドリング: ZipExceptionの原因を特定し、パス指定やメモリ管理を見直す。

JavaでのZIP作成は、一度基本を理解すればさまざまな要件に応用できます。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

-Java入門