JDK 自帶 java.util.zip 在處理中文路徑編碼時存在已知缺陷(早期版本僅支持 UTF-8,且行為不一致)。Apache Ant 的 org.apache.tools.zip 包和 Expand 任務提供:
setEncoding("UTF-8"))<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.10.14</version> <!-- 或更新版本 -->
</dependency>
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Expand;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import java.io.*;
public class ZipUtil {
private static final String ENCODING = "UTF-8";
/** 壓縮文件/目錄 */
public static void zip(String inputPath, String zipPath) {
File inputFile = new File(inputPath);
if (!inputFile.exists()) throw new RuntimeException("源文件不存在: " + inputPath);
File zipFile = new File(zipPath);
if (!zipFile.getParentFile().exists() && !zipFile.getParentFile().mkdirs())
throw new RuntimeException("無法創建目標目錄: " + zipFile.getParent());
try (FileOutputStream fos = new FileOutputStream(zipFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
ZipOutputStream zos = new ZipOutputStream(bos, ENCODING)) { // Ant ZipOutputStream 支持編碼構造參數
zip(zos, inputFile, inputFile.getName());
zos.finish(); // 關鍵:寫入中央目錄
} catch (IOException e) {
throw new RuntimeException("壓縮失敗", e);
}
}
private static void zip(ZipOutputStream zos, File file, String base) throws IOException {
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
zip(zos, child, base + "/" + child.getName());
}
}
} else {
zos.putNextEntry(new ZipEntry(base));
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] buffer = new byte[8192];
int len;
while ((len = bis.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}
}
zos.closeEntry();
}
}
/** 解壓 ZIP 到指定目錄 */
public static void unzip(String zipPath, String destDir) {
File zipFile = new File(zipPath);
if (!zipFile.exists()) throw new RuntimeException("ZIP 文件不存在: " + zipPath);
File dest = new File(destDir);
if (!dest.exists() && !dest.mkdirs()) throw new RuntimeException("無法創建解壓目錄: " + destDir);
Project proj = new Project();
Expand expand = new Expand();
expand.setProject(proj);
expand.setSrc(zipFile);
expand.setDest(dest);
expand.setEncoding(ENCODING);
expand.setOverwrite(true); // 覆蓋已存在文件
expand.execute(); // 核心一行
}
}
new ZipOutputStream(bos, ENCODING) 顯式指定 UTF-8,解決中文文件名亂碼。finish() 僅寫入中央目錄結束標記,不關閉底層流;配合 try-with-resources 由外層流關閉。setOverwrite(true) 避免文件存在報錯。new Project() 必須設置給 Expand,否則 NPE。public class ZipUtilTest {
@Test
public void testZipAndUnzip() throws IOException {
File tempDir = Files.createTempDirectory("ziptest").toFile();
File srcFile = new File(tempDir, "source.txt");
Files.writeString(srcFile.toPath(), "中文測試內容");
String zipPath = new File(tempDir, "archive.zip").getAbsolutePath();
String destDir = new File(tempDir, "out").getAbsolutePath();
ZipUtil.zip(srcFile.getAbsolutePath(), zipPath);
ZipUtil.unzip(zipPath, destDir);
File outFile = new File(destDir, "source.txt");
assertTrue(outFile.exists());
assertEquals("中文測試內容", Files.readString(outFile.toPath()));
}
}
| 方案 | 中文支持 | API 簡潔度 | 依賴大小 | 推薦場景 |
|---|---|---|---|---|
| JDK java.util.zip | 需 JDK 7+ 顯式編碼,歷史版本有坑 | 較繁瑣 | 零依賴 | 簡單項目、不可引入第三方 |
| Apache Ant (本文) | 原生支持,穩定 | 解壓極簡,壓縮需遞歸 | ~1.5MB | 企業級項目、需可靠中文支持 |
| Apache Commons Compress | 完美支持 | 豐富、標準 | ~500KB | 現代項目、支持多種格式(tar/gzip/7z) |
| ZeroTurnaround zipt | 完美 | 最簡單 | ~100KB | 輕量級、只需壓縮解壓 |