概要
ファイルの圧縮と解凍は、帳票のまとめ送信・ログファイルのアーカイブ・API レスポンスの GZIP 圧縮など、業務システムで日常的に必要になる処理です。Java には java.util.zip パッケージが標準で用意されており、外部ライブラリなしで ZIP と GZIP の両方を扱えます。ただし、ZipEntry の closeEntry 忘れ、ストリームのクローズ順序、文字コード指定の漏れなど、動作はするが不具合の温床になるコードを書きやすい領域でもあります。この記事では、複数ファイルの ZIP 圧縮・展開と GZIP の単一データ圧縮・解凍を、try-with-resources によるリソース管理を含めた安全なパターンで整理します。圧縮率の目安やバッファサイズの選び方など、実運用で気になるポイントも補足します。
使いどころ
月次バッチで生成した複数の帳票ファイル(PDF・CSV)を1つの ZIP にまとめ、メール添付用に出力する
サーバー間のログ転送で GZIP 圧縮をかけ、転送量を削減する
外部システムから受信した ZIP ファイルを展開し、中のCSVを1件ずつ取り込みバッチ処理する
コード例
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.*;
public class ZipGzipExample {
record ZipEntryData(String name, String content) {}
/** 複数エントリを1つの ZIP に圧縮 */
public static byte[] createZip(List<ZipEntryData> entries) throws IOException {
var baos = new ByteArrayOutputStream();
try (var zos = new ZipOutputStream(baos)) {
for (var entry : entries) {
zos.putNextEntry(new ZipEntry(entry.name()));
zos.write(entry.content().getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
}
}
return baos.toByteArray();
}
/** ZIP を展開して List<ZipEntryData> として返す */
public static List<ZipEntryData> readZip(byte[] zipData) throws IOException {
var result = new ArrayList<ZipEntryData>();
try (var zis = new ZipInputStream(new ByteArrayInputStream(zipData))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
var content = new ByteArrayOutputStream();
var buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) != -1) {
content.write(buffer, 0, len);
}
result.add(new ZipEntryData(entry.getName(),
content.toString(StandardCharsets.UTF_8)));
zis.closeEntry();
}
}
return result;
}
/** 文字列を GZIP 圧縮 */
public static byte[] gzipCompress(String text) throws IOException {
var baos = new ByteArrayOutputStream();
try (var gos = new GZIPOutputStream(baos)) {
gos.write(text.getBytes(StandardCharsets.UTF_8));
}
return baos.toByteArray();
}
/** GZIP 解凍 */
public static String gzipDecompress(byte[] compressed) throws IOException {
var baos = new ByteArrayOutputStream();
try (var gis = new GZIPInputStream(new ByteArrayInputStream(compressed))) {
var buffer = new byte[1024];
int len;
while ((len = gis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
}
return baos.toString(StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
// ZIP 圧縮・解凍
var entries = List.of(
new ZipEntryData("report.csv", "id,name\n1,田中\n2,鈴木"),
new ZipEntryData("memo.txt", "処理完了")
);
var zip = createZip(entries);
System.out.println("ZIP サイズ: " + zip.length + " bytes");
var extracted = readZip(zip);
extracted.forEach(e ->
System.out.println(e.name() + ": " + e.content()));
// GZIP 圧縮・解凍
var text = "GZIP 圧縮テスト".repeat(50);
var compressed = gzipCompress(text);
var original = text.getBytes(StandardCharsets.UTF_8).length;
System.out.printf("GZIP: %d → %d bytes (%.0f%%削減)%n",
original, compressed.length,
(1.0 - (double) compressed.length / original) * 100);
System.out.println("解凍一致: " + text.equals(gzipDecompress(compressed)));
}
}Version Coverage
var と record を使って ZIP エントリのデータを構造化できる。List.of によるエントリリストの作成も簡潔になる。String.repeat で圧縮テスト用データの生成が楽。
// Java 17: record + List.of でエントリを構造化
record ZipEntryData(String name, String content) {}
var entries = List.of(
new ZipEntryData("file1.txt", "内容1"),
new ZipEntryData("file2.csv", "内容2")
);
var baos = new ByteArrayOutputStream();
try (var zos = new ZipOutputStream(baos)) {
for (var entry : entries) {
zos.putNextEntry(new ZipEntry(entry.name()));
zos.write(entry.content().getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
}
}Library Comparison
注意点
ZipOutputStream で putNextEntry した後、closeEntry を呼ばずに次の putNextEntry を呼ぶと、ZIP ファイルが壊れる可能性がある。エントリごとに必ず closeEntry すること
ZipInputStream で読み込んだ際、entry.getCompressedSize() が -1 を返す場合がある。ストリーム読み込みでは圧縮サイズが事前にわからないケースがあるため、サイズに依存したバッファ確保は避けること
日本語ファイル名を含む ZIP は文字コードの問題が起きやすい。Java 標準の ZipOutputStream は UTF-8 をデフォルトで使うが、Windows のエクスプローラーで文字化けする場合がある
GZIP 圧縮では GZIPOutputStream.close() の前に flush() を呼ばなくても close 内で暗黙に行われるが、try-with-resources を使わない場合は明示的に close しないと圧縮データが不完全になる
巨大ファイルをメモリ上で ByteArrayOutputStream に圧縮すると OutOfMemoryError の原因になる。実運用ではファイルストリームに直接書き出す設計を検討すること
FAQ
複数ファイルをまとめたい場合は ZIP、単一データの圧縮転送には GZIP が適しています。HTTP レスポンスの圧縮は GZIP が標準的です。
ZipOutputStream.setLevel() で Deflater.BEST_SPEED(1)から BEST_COMPRESSION(9)まで指定できます。デフォルトは DEFAULT_COMPRESSION(6相当)で、多くの場合これで十分です。
ヒープサイズに依存しますが、目安として数十MB以下であれば ByteArrayOutputStream で問題になることは少ないです。それ以上の場合はファイルストリームへの直接出力を検討してください。