如何使用Java实现文件压缩与解压_JavaIO综合项目讲解
#技术教程 发布时间: 2026-01-17
java.util.zip可完成ZIP压缩解压但需手动处理流、编码、目录结构和中文路径;压缩时路径须标准化、递归处理目录、正确调用putNextEntry/closeEntry;解压需防路径穿越、校验合法性;中文名需统一UTF-8或按需fallback GBK;Jar类仅适用于Java应用打包。
Java 标准库的 java.util.zip 包能完成基础 ZIP 压缩与解压,无需第三方依赖,但必须手动处理流、编码、目录结构和中文路径问题——多数失败都卡在这四点上。
用 ZipOutputStream 压缩多个文件或目录
核心是把每个条目(ZipEntry)写入输出流,注意路径标准化和文件内容读取顺序:
-
ZipEntry的name必须用正斜杠/分隔,不能含盘符或开头的\或/(如"docs/readme.txt"合法,"C:\\docs\\readme.txt"或"/docs/readme.txt"会出错) - 压缩目录时需递归遍历,对子目录也创建一个带结尾
/的ZipEntry(如"images/"),否则解压后目录丢失 - 写入文件内容前必须先调用
putNextEntry(),且每个entry只能写一次;写完要调用closeEntry() - 若源文件含中文名,
ZipOutputStream默认使用 IBM437 编码,Windows 下需改用ZipOutputStream(OutputStream, Charset.forName("GBK"))(JDK 7+ 支持)
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("out.zip"), StandardCharsets.UTF_8)) {
addFileToZip(zos, Paths.get("src"), "");
} catch (IOException e) {
e.printStackTrace();
}
void addFileToZip(ZipOutputStream zos, Path file, String prefix) throws IOException {
String entryName = prefix + file.getFileName().toString();
if (Files.isDirectory(file)) {
zos.putNextEntry(new ZipEntry(entryName + "/"));
zos.closeEntry();
try (Stream stream = Files.list(file)) {
stream.forEach(child -> {
try {
addFileToZip(zos, child, entryName + "/");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
} else {
zos.putNextEntry(new ZipEntry(entryName));
Files.copy(file, zos);
zos.closeEntry();
}
}
用 ZipInputStream 安全解压 ZIP 文件
不能直接按 ZipEntry.getName() 创建 File,否则可能被路径穿越攻击(如 "../etc/passwd");同时要校验条目类型和目标路径合法性:
- 跳过
isDirectory()为true的条目(目录本身不包含数据,只靠路径名隐式存在) - 对每
个
entry.getName()调用Paths.get(entry.getName()).normalize(),再检查是否仍以目标解压根目录为前缀 - 禁止解压到系统敏感路径(如
"C:\\"、"/etc/"),建议统一解压到临时子目录 - 读取内容时用
ZipInputStream.read(byte[])循环,不要一次性readAllBytes()——大文件会 OOM
Path targetDir = Paths.get("unzipped");
Files.createDirectories(targetDir);
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("in.zip"), StandardCharsets.UTF_8)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
Path targetFile = targetDir.resolve(entry.getName()).normalize();
// 防穿越:确保 targetFile 仍在 targetDir 下
if (!targetFile.startsWith(targetDir.toAbsolutePath().normalize())) {
throw new IOException("Bad zip entry: " + entry.getName());
}
if (entry.isDirectory()) {
Files.createDirectories(targetFile);
} else {
Files.createDirectories(targetFile.getParent());
Files.copy(zis, targetFile, StandardCopyOption.REPLACE_EXISTING);
}
zis.closeEntry();
}
}
处理 ZIP 中的中文文件名乱码问题
根本原因是 ZIP 规范未强制指定编码,不同操作系统/工具默认不同:Windows 通常用 GBK,macOS/Linux 多用 UTF-8。JDK 7+ 的 ZipInputStream/ZipOutputStream 构造函数支持传入 Charset,但旧版 JDK(≤6)或某些 ZIP 工具生成的包仍可能不兼容:
- 若已知 ZIP 由 Windows 上 7-Zip 或 WinRAR 生成,优先试
Charset.forName("GBK") - 若不确定,可先用
ZipInputStream读取条目名,按 UTF-8 解码失败后再用 GBK 尝试(需自己封装 fallback 逻辑) - 避免用
FileInputStream+ZipInputStream组合去“猜”编码——流已消费不可重置,应改用ByteArrayInputStream缓存原始字节再多次尝试 - 真正跨平台稳定的方案是:压缩端统一用 UTF-8(JDK 7+ 设置
StandardCharsets.UTF_8),解压端也用 UTF-8;若必须兼容老 ZIP,则需引入ant.jar的ZipFile(支持自动检测)或TrueZip
为什么不用 java.util.jar?
JarOutputStream 和 JarInputStream 是 ZipOutputStream/ZipInputStream 的子类,仅额外支持 META-INF/MANIFEST.MF。普通压缩解压完全没必要用它:
- 写 JAR 会强制在 ZIP 根目录加
META-INF/目录,哪怕你没提供清单文件 - 读 JAR 时若 ZIP 不含
META-INF/MANIFEST.MF,JarInputStream会抛IOException(而ZipInputStream不会) - 性能无差异,API 几乎一致,但语义混淆——除非你在打包 Java 应用,否则坚持用
zip相关类
最常被忽略的是:ZIP 条目名编码不是流的字符集,而是 ZIP 文件元数据的编码;ZipInputStream 构造时传的 Charset 只影响 getEntry().getName() 返回值,不影响文件内容解码——内容始终是原始字节流,该用什么编码读文件内容,和 ZIP 编码无关。
上一篇 : Java自学网站_适合自学的Java学习网站汇总
下一篇 : 小米SU7领跑2025年国内中大型汽车市场 超越奥迪A6L
-
SEO外包最佳选择国内专业的白帽SEO机构,熟知搜索算法,各行业企业站优化策略!
SEO公司
-
可定制SEO优化套餐基于整站优化与品牌搜索展现,定制个性化营销推广方案!
SEO套餐
-
SEO入门教程多年积累SEO实战案例,从新手到专家,从入门到精通,海量的SEO学习资料!
SEO教程
-
SEO项目资源高质量SEO项目资源,稀缺性外链,优质文案代写,老域名提权,云主机相关配置折扣!
SEO资源
-
SEO快速建站快速搭建符合搜索引擎友好的企业网站,协助备案,域名选择,服务器配置等相关服务!
SEO建站
-
快速搜索引擎优化建议没有任何SEO机构,可以承诺搜索引擎排名的具体位置,如果有,那么请您多注意!专业的SEO机构,一般情况下只能确保目标关键词进入到首页或者前几页,如果您有相关问题,欢迎咨询!
