如何使用Java实现文件压缩与解压_JavaIO综合项目讲解_技术教程_七洗推广网

如何使用Java实现文件压缩与解压_JavaIO综合项目讲解

#技术教程 发布时间: 2026-01-17
java.util.zip可完成ZIP压缩解压但需手动处理流、编码、目录结构和中文路径;压缩时路径须标准化、递归处理目录、正确调用putNextEntry/closeEntry;解压需防路径穿越、校验合法性;中文名需统一UTF-8或按需fallback GBK;Jar类仅适用于Java应用打包。

Java 标准库的 java.util.zip 包能完成基础 ZIP 压缩与解压,无需第三方依赖,但必须手动处理流、编码、目录结构和中文路径问题——多数失败都卡在这四点上。

ZipOutputStream 压缩多个文件或目录

核心是把每个条目(ZipEntry)写入输出流,注意路径标准化和文件内容读取顺序:

  • ZipEntryname 必须用正斜杠 / 分隔,不能含盘符或开头的 \/(如 "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.jarZipFile(支持自动检测)或 TrueZip

为什么不用 java.util.jar

JarOutputStreamJarInputStreamZipOutputStream/ZipInputStream 的子类,仅额外支持 META-INF/MANIFEST.MF。普通压缩解压完全没必要用它:

  • 写 JAR 会强制在 ZIP 根目录加 META-INF/ 目录,哪怕你没提供清单文件
  • 读 JAR 时若 ZIP 不含 META-INF/MANIFEST.MFJarInputStream 会抛 IOException(而 ZipInputStream 不会)
  • 性能无差异,API 几乎一致,但语义混淆——除非你在打包 Java 应用,否则坚持用 zip 相关类

最常被忽略的是:ZIP 条目名编码不是流的字符集,而是 ZIP 文件元数据的编码;ZipInputStream 构造时传的 Charset 只影响 getEntry().getName() 返回值,不影响文件内容解码——内容始终是原始字节流,该用什么编码读文件内容,和 ZIP 编码无关。

技术教程SEO

上一篇 : Java自学网站_适合自学的Java学习网站汇总

下一篇 : 小米SU7领跑2025年国内中大型汽车市场 超越奥迪A6L
品牌营销
专业SEO优化
添加左侧专家微信
获取产品详细报价方案