Java Tips | I/O・ネットワーク:gzip解凍

Java Java
スポンサーリンク

なぜ「gzip解凍ユーティリティ」が業務で必須になるのか

業務システムでは、ログ、CSV、JSON、バックアップファイルなどを gzip で圧縮して保存・転送することがよくあります。 その結果、「読むときは必ず gzip を解凍する」という処理が、あちこちに散らばりがちになります。

毎回その場で GZIPInputStream を直接書いていると、 ストリームの閉じ忘れ、例外処理のバラつき、文字コードの扱いミスなどが起きやすくなります。 だからこそ、「gzip解凍」を小さなユーティリティとしてまとめておくと、業務コードがすっきりし、バグも減ります。

基本形:gzipファイルを丸ごと解凍して別ファイルに書き出す

GZIPInputStream と Files.write を組み合わせる

まずは一番分かりやすい、「xxx.gz を解凍して元のファイルを作る」ユーティリティから押さえましょう。

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.GZIPInputStream;

public class GzipUtils {

    public static void decompress(Path inputGzip, Path outputFile) throws IOException {
        try (InputStream in = new GZIPInputStream(Files.newInputStream(inputGzip))) {
            byte[] bytes = in.readAllBytes();
            Files.write(outputFile, bytes);
        }
    }
}
Java

使い方の例です。

import java.nio.file.Path;

public class DecompressExample {

    public static void main(String[] args) throws Exception {
        Path input  = Path.of("data/report.csv.gz");
        Path output = Path.of("data/report.csv");

        GzipUtils.decompress(input, output);
    }
}
Java

ここで重要なのは、「GZIPInputStream は普通の InputStream と同じように扱える」ということです。 new GZIPInputStream(...) でラップした瞬間、read() で読み出されるバイトはすでに解凍済みになります。

もう一つのポイントは、try-with-resources でストリームを必ず閉じていることです。 解凍処理は I/O を伴うので、ストリームを閉じ忘れるとファイルハンドルが枯渇し、長期運用で障害につながります。

大きなファイルを「ストリームで解凍」する形

readAllBytes を避けて、少しずつ読みながら書き出す

先ほどの例はシンプルですが、巨大な gzip ファイルでは readAllBytes() がメモリを圧迫します。 そこで、「少しずつ読みながら書き出す」ストリーム解凍ユーティリティを用意します。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.GZIPInputStream;

public class GzipStreamUtils {

    public static void decompressStream(Path inputGzip, Path outputFile) throws IOException {
        try (InputStream in = new GZIPInputStream(Files.newInputStream(inputGzip));
             OutputStream out = Files.newOutputStream(outputFile)) {

            byte[] buffer = new byte[8192];
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        }
    }
}
Java

ここで深掘りしたいのは、「部分読み込みと部分書き込み」の扱いです。 in.read(buffer) は、バッファサイズぴったり読めるとは限らず、「今回実際に読めたバイト数」を返します。 必ず out.write(buffer, 0, len) として、「有効な範囲だけ」書き出す必要があります。 これを怠ると、解凍されたファイルにゴミが混ざり、データが壊れます。

gzip解凍したデータをそのままメモリで扱うユーティリティ

「解凍して byte[] を返す」形

ファイルに書き出さず、「解凍した中身をメモリでそのまま扱いたい」場面もあります。 例えば、gzip圧縮された JSON を解凍して、そのままパースしたい場合です。

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.GZIPInputStream;

public class GzipMemoryUtils {

    public static byte[] decompressToBytes(Path inputGzip) throws IOException {
        try (InputStream in = new GZIPInputStream(Files.newInputStream(inputGzip))) {
            return in.readAllBytes();
        }
    }
}
Java

使い方の例です。

import java.nio.charset.StandardCharsets;
import java.nio.file.Path;

public class DecompressToBytesExample {

    public static void main(String[] args) throws Exception {
        Path input = Path.of("data/data.json.gz");
        byte[] bytes = GzipMemoryUtils.decompressToBytes(input);

        String json = new String(bytes, StandardCharsets.UTF_8);
        System.out.println(json);
    }
}
Java

ここで重要なのは、「文字列にするときは必ず文字コードを指定する」ことです。 new String(bytes) とすると環境依存になり、文字化けや本番との挙動差の原因になります。 業務システムでは、ほぼ常に UTF-8 に統一するのがおすすめです。

gzip解凍とセキュリティ・運用の観点

展開先パスを必ず制御する

解凍ユーティリティは「どこに書き出すか」を決める必要があります。 外部入力からパスをそのまま受け取ると、 ../../ を使ったディレクトリトラバーサル攻撃で、意図しない場所にファイルを書かれる危険があります。

実務では、次のような方針を取るのが安全です。

アプリケーションが書いてよいベースディレクトリを決める。 ユーティリティは、その配下にしか書き出さないようにする。 ファイル名は外部入力から受け取っても、ディレクトリは固定する。

こうしておくと、「gzip解凍ユーティリティがどこに何を書いているか」が明確になり、監査や運用の観点でも安心できます。

解凍前にサイズや拡張子をチェックする

gzipファイルは、圧縮前のサイズが非常に大きい場合があります。 悪意ある入力(いわゆる“圧縮爆弾”)を解凍すると、ディスクやメモリを一気に消費してしまうことがあります。

実務では、次のような対策が有効です。

アップロードされた gzip ファイルのサイズ上限を決める。 解凍先のディスク容量を監視し、閾値を超えたら警告・停止する。 用途に応じて拡張子(.gz)や MIME タイプをチェックする。

ユーティリティ単体ではなく、運用設計とセットで考えることで、 gzip解凍を安全に業務フローに組み込めます。

まとめ:gzip解凍ユーティリティで身につけてほしい感覚

gzip解凍ユーティリティは、「圧縮されたデータを安全に元に戻す」ための小さな道具ですが、 その裏には次のような大事なポイントが詰まっています。

GZIPInputStream は「解凍済みバイトを返す InputStream」である。 小さなデータは readAllBytes、大きなデータはバッファを使ったストリーム解凍にする。 ストリームは try-with-resources で必ず閉じ、リソースリークを防ぐ。 解凍したテキストを扱うときは、文字コード(UTF-8など)を必ず指定する。 展開先パスやサイズ上限など、セキュリティ・運用のルールとセットで設計する。

もしあなたのプロジェクトで、 あちこちにバラバラな GZIPInputStream のコードが散らばっているなら、 それを一度「業務用 gzip解凍ユーティリティ」にまとめられないか眺めてみてください。

そこから先は、圧縮データの扱いがぐっと安定し、 ログ・CSV・JSON・バックアップなど、サイズの大きなデータを安心して運用できるようになっていきます。

タイトルとURLをコピーしました