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

Java入門

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

トム

・SaaS勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

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

システム開発において、このようなファイル圧縮の要件が出てくる場面は少なくありません。私自身も、Webアプリケーション開発で帳票ファイルを一括ダウンロードさせる機能を実装する際、JavaでのZIP作成方法を学びました。しかし、当時はファイル名の文字化けや、大きなファイルを扱う際のメモリ不足など、数々のエラーに悩まされた経験があります。

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

この記事を最後まで読めば、Javaの標準機能を使った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()で閉じます。

この一連の流れを制御することで、単一ファイルからディレクトリまで、柔軟なjavazip作成が実現できます。

サンプルコードで理解する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の一種)の指定が有効です。

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

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

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

残念ながら、Javaの標準APIjava.util.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();
        }
    }
}

このように、ライブラリを使えば複雑な暗号化処理を自前で実装する必要がなく、手軽に安全なZIPファイルを作成できます。

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

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

Java 7から導入されたNIO.2の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にファイルを追加しました。");
        }
    }
}

この方法は、ZIP内の特定ファイルだけを更新したい場合などにも非常に有効です。

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

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

サンプルコードで示してきたように、FileInputStreamから固定サイズのバッファ(例:byte[1024])を使って少しずつデータを読み込み、ZipOutputStreamに書き出す「ストリーミング処理」を徹底することが重要です。

一度に全ファイルの内容をメモリ上に展開しないことで、ヒープ領域の消費を最小限に抑え、安定した処理が可能になります。

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作成は、一度基本を理解すれば様々な要件に応用できます。

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

トム

・SaaS勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門