GZIP 是 HTTP、日誌、訊息隊列、物件序列化最常用的無損壓縮格式。JDK java.util.zip.GZIPInputStream/OutputStream 提供流式 API,但直接使用時易漏關閉資源、緩衝區大小固定、檔案與位元組陣列需重寫邏輯。本文封裝一個執行緒安全、資源自動管理、支援檔案/位元組雙模式、可選擇刪除源檔的生產級工具類 GZipUtils。
package com.cjonline.foundation.cpe.action;
import java.io.*;
import java.util.zip.*;
public abstract class GZipUtils {
public static final int BUFFER = 8192; // 8KB,平衡記憶體與系統呼叫
public static final String EXT = ".gz";
/* ========== 位元組陣列模式 ========== */
public static byte[] compress(byte[] data) throws IOException {
try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPOutputStream gos = new GZIPOutputStream(bos)) {
copy(bis, gos);
gos.finish(); // 關鍵:寫入 GZIP footer
return bos.toByteArray();
}
}
public static byte[] decompress(byte[] data) throws IOException {
try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPInputStream gis = new GZIPInputStream(bis)) {
copy(gis, bos);
return bos.toByteArray();
}
}
/* ========== 檔案模式 ========== */
public static void compress(File file) throws IOException { compress(file, true); }
public static void compress(File file, boolean deleteSource) throws IOException {
File out = new File(file.getAbsolutePath() + EXT);
try (FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(out);
GZIPOutputStream gos = new GZIPOutputStream(fos)) {
copy(fis, gos);
gos.finish();
}
if (deleteSource && !file.delete()) throw new IOException("無法刪除源檔: " + file);
}
public static void decompress(File file) throws IOException { decompress(file, true); }
public static void decompress(File file, boolean deleteSource) throws IOException {
if (!file.getName().endsWith(EXT)) throw new IllegalArgumentException("非 .gz 檔案");
File out = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - EXT.length()));
try (FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(out);
GZIPInputStream gis = new GZIPInputStream(fis)) {
copy(gis, fos);
}
if (deleteSource && !file.delete()) throw new IOException("無法刪除源檔: " + file);
}
// 路徑字串重載
public static void compress(String path) throws IOException { compress(new File(path)); }
public static void compress(String path, boolean delete) throws IOException { compress(new File(path), delete); }
public static void decompress(String path) throws IOException { decompress(new File(path)); }
public static void decompress(String path, boolean delete) throws IOException { decompress(new File(path), delete); }
/* ========== 核心流拷貝 ========== */
private static void copy(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[BUFFER];
int len;
while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
}
}
| 特性 | 實現細節 | 收益 |
|---|---|---|
| try-with-resources | 所有 Stream 宣告在 try() 區塊 | 異常/正常返回均自動關閉,零洩漏 |
| 緩衝區 8KB | BUFFER = 8192 | 減少系統呼叫,適配磁碟頁面/網路 MTU |
| gos.finish() | 壓縮結束前呼叫 | 確保寫入 CRC-32、ISIZE 等 GZIP footer,否則解壓報 "not in GZIP format" |
| 刪除源檔可選 | deleteSource 參數 | 支援「壓縮備份」與「壓縮替換」兩種業務場景 |
| 輸入校驗 | 解壓檢查 .gz 副檔名 | 快速失敗,避免誤操作非 GZIP 檔案 |
| 執行緒安全 | 無共享可變狀態,所有區域變數 | 可直接在並發環境共用 |
// 1. 位元組陣列壓縮/解壓 (如 Redis 快取、訊息體)
byte[] compressed = GZipUtils.compress(json.getBytes(StandardCharsets.UTF_8));
byte[] original = GZipUtils.decompress(compressed);
// 2. 檔案壓縮備份 (保留源檔)
GZipUtils.compress(new File("/var/log/app.log"), false);
// 3. 檔案解壓並刪除 .gz
GZipUtils.decompress("/tmp/data.gz", true);
// 4. 大檔案流式處理 (不載入記憶體)
GZipUtils.compress(new File("huge.csv"), new File("huge.csv.gz")); // 需自行擴充多參數版本
| 方案 | 壓縮率 | 速度 | CPU | 適用場景 |
|---|---|---|---|---|
| JDK GZIP (DEFLATE) | 中等 | 中等 | 中 | 通用、相容性最好、HTTP 標準 |
| Snappy | 較低 | 極快 | 低 | 即時壓縮、Kafka/RocksDB 內建 |
| LZ4 | 較低 | 極快 | 極低 | 高吞吐日誌、資料庫頁面壓縮 |
| Zstandard (zstd) | 高 | 快 | 中 | 現代替代者,支援字典、多執行緒 |
若專案對壓縮率/速度敏感,建議引入lz4-java或zstd-jni,API 與 GZIP 相似,只需替換 Stream 實現類。
finish()、或雙重壓縮。一個健壯的 GZipUtils 能讓團隊在日誌輪轉、快取壓縮、API 傳輸、檔案備份等場景「拿來即用、零 Bug」。核心原則:**流式處理、try-with-resources、finish() 收尾、緩衝區適中、輸入校驗、執行緒安全**理解這些細節,也能輕鬆遷移到 LZ4、Zstd 等現代壓縮算法。