zip压缩

Java 压缩与解压缩操作详解:ZIP 与 GZIP 实践

在 Java 中,处理压缩文件是常见需求,尤其是在文件传输、存储优化等场景。JDK 内置了对 ZIP 和 GZIP 格式的支持,通过 java.util.zip 包中的类可实现压缩和解压缩操作。本文将详细介绍 ZIP 和 GZIP 的使用方法,包括单文件 / 多文件压缩、文件夹递归压缩及解压缩的具体实现。

ZIP 压缩与解压缩

ZIP 是一种广泛使用的归档格式,支持将多个文件或文件夹压缩到一个 .zip 文件中,且可保留目录结构。Java 中通过 ZipInputStream(解压缩)和 ZipOutputStream(压缩)实现 ZIP 操作。

ZIP 解压缩(ZipInputStream

ZipInputStream 用于读取 ZIP 文件中的条目(文件或文件夹),通过 getNextEntry() 遍历所有条目,再通过输入流读取具体内容。

核心步骤:
  1. 创建 ZipInputStream 关联 ZIP 文件输入流。
  2. 循环调用 getNextEntry() 获取每个 ZipEntry(条目)。
  3. 对每个条目,通过 ZipInputStream 读取数据(若为文件夹则跳过内容读取)。
  4. 处理完成后调用 closeEntry() 关闭当前条目,最终关闭流。
示例:解压缩 ZIP 文件到指定目录
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipUncompressor {
    /**
     * 解压缩 ZIP 文件到目标目录
     * @param zipFilePath ZIP 文件路径
     * @param destDir 目标目录
     */
    public static void unzip(String zipFilePath, String destDir) throws IOException {
        // 创建目标目录(不存在则创建)
        File destDirFile = new File(destDir);
        if (!destDirFile.exists()) {
            destDirFile.mkdirs();
        }

        try (ZipInputStream zis = new ZipInputStream(
                new BufferedInputStream(new FileInputStream(zipFilePath)),
                StandardCharsets.UTF_8)) { // 指定编码(解决中文乱码)

            ZipEntry entry;
            // 遍历所有条目
            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();
                File entryFile = new File(destDirFile, entryName);

                if (entry.isDirectory()) {
                    // 若为文件夹,创建目录
                    entryFile.mkdirs();
                } else {
                    // 若为文件,读取内容并写入目标文件
                    // 确保父目录存在
                    File parentDir = entryFile.getParentFile();
                    if (!parentDir.exists()) {
                        parentDir.mkdirs();
                    }

                    try (BufferedOutputStream bos = new BufferedOutputStream(
                            new FileOutputStream(entryFile))) {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = zis.read(buffer)) != -1) {
                            bos.write(buffer, 0, len);
                        }
                    }
                }
                zis.closeEntry(); // 关闭当前条目
            }
        }
    }

    public static void main(String[] args) throws IOException {
        unzip("test.zip", "unzip_result");
        System.out.println("解压缩完成!");
    }
}
注意事项:
  • 中文乱码ZipInputStream 构造器需指定编码(如 StandardCharsets.UTF_8),避免条目名称中文乱码。
  • 目录处理:通过 entry.isDirectory() 判断是否为文件夹,需创建对应目录结构。

ZIP 压缩(ZipOutputStream

ZipOutputStream 用于将文件或文件夹压缩为 ZIP 文件,通过 putNextEntry() 写入条目信息,再写入具体内容。

核心步骤:
  1. 创建 ZipOutputStream 关联目标 ZIP 文件输出流。
  2. 对每个需要压缩的文件 / 文件夹,创建 ZipEntry 并调用 putNextEntry()
  3. 写入文件内容(文件夹无需写入内容,仅创建条目)。
  4. 调用 closeEntry() 关闭当前条目,最终关闭流。
示例:压缩文件或文件夹为 ZIP
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipCompressor {
    /**
     * 压缩文件或文件夹为 ZIP
     * @param sourcePath 源文件/文件夹路径
     * @param zipFilePath 目标 ZIP 文件路径
     */
    public static void zip(String sourcePath, String zipFilePath) throws IOException {
        File sourceFile = new File(sourcePath);
        if (!sourceFile.exists()) {
            throw new FileNotFoundException("源文件/文件夹不存在:" + sourcePath);
        }

        try (ZipOutputStream zos = new ZipOutputStream(
                new BufferedOutputStream(new FileOutputStream(zipFilePath)),
                StandardCharsets.UTF_8)) { // 指定编码

            // 递归压缩(根据源是否为文件夹处理)
            if (sourceFile.isDirectory()) {
                // 压缩文件夹(传入父路径为空,避免顶级目录冗余)
                compressDirectory(zos, sourceFile, "");
            } else {
                // 压缩单个文件
                compressFile(zos, sourceFile, "");
            }
        }
    }

    /**
     * 递归压缩文件夹
     * @param zos ZIP 输出流
     * @param dir 待压缩文件夹
     * @param parentEntryName 父条目名称(用于构建条目路径)
     */
    private static void compressDirectory(ZipOutputStream zos, File dir, String parentEntryName) throws IOException {
        File[] files = dir.listFiles();
        if (files == null) {
            return; // 空文件夹
        }

        // 构建当前文件夹的条目名称(末尾加斜杠表示文件夹)
        String dirEntryName = parentEntryName + dir.getName() + File.separator;
        zos.putNextEntry(new ZipEntry(dirEntryName));
        zos.closeEntry(); // 文件夹条目无需内容,直接关闭

        // 递归处理子文件/子文件夹
        for (File file : files) {
            if (file.isDirectory()) {
                compressDirectory(zos, file, dirEntryName);
            } else {
                compressFile(zos, file, dirEntryName);
            }
        }
    }

    /**
     * 压缩单个文件
     * @param zos ZIP 输出流
     * @param file 待压缩文件
     * @param parentEntryName 父条目名称(用于构建条目路径)
     */
    private static void compressFile(ZipOutputStream zos, File file, String parentEntryName) throws IOException {
        String entryName = parentEntryName + file.getName();
        zos.putNextEntry(new ZipEntry(entryName)); // 写入条目信息

        // 写入文件内容
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                zos.write(buffer, 0, len);
            }
        }
        zos.closeEntry(); // 关闭当前条目
    }

    public static void main(String[] args) throws IOException {
        // 压缩单个文件
        zip("test.txt", "single_file.zip");
        // 压缩文件夹
        zip("docs", "docs.zip");
        System.out.println("压缩完成!");
    }
}
注意事项:
  • 条目路径:通过 parentEntryName 维护目录结构,确保解压后目录层级正确。
  • 文件夹标识:文件夹条目名称需以 File.separator(如 /\)结尾,否则解压时可能被识别为文件。

GZIP 压缩与解压缩

GZIP 是一种用于压缩单个文件的格式(不支持多文件或文件夹),压缩后文件通常以 .gz 为扩展名。Java 中通过 GZIPInputStream(解压缩)和 GZIPOutputStream(压缩)实现。

GZIP 压缩(GZIPOutputStream

示例:压缩单个文件为 GZIP
import java.io.*;
import java.util.zip.GZIPOutputStream;

public class GzipCompressor {
    /**
     * 压缩单个文件为 GZIP
     * @param sourceFilePath 源文件路径
     * @param gzipFilePath 目标 GZIP 文件路径
     */
    public static void gzip(String sourceFilePath, String gzipFilePath) throws IOException {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFilePath));
             GZIPOutputStream gzos = new GZIPOutputStream(
                     new BufferedOutputStream(new FileOutputStream(gzipFilePath)))) {

            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                gzos.write(buffer, 0, len);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        gzip("data.txt", "data.txt.gz");
        System.out.println("GZIP 压缩完成!");
    }
}

GZIP 解压缩(GZIPInputStream

示例:解压缩 GZIP 文件
import java.io.*;
import java.util.zip.GZIPInputStream;

public class GzipUncompressor {
    /**
     * 解压缩 GZIP 文件
     * @param gzipFilePath GZIP 文件路径
     * @param destFilePath 目标文件路径
     */
    public static void ungzip(String gzipFilePath, String destFilePath) throws IOException {
        try (GZIPInputStream gzis = new GZIPInputStream(
                new BufferedInputStream(new FileInputStream(gzipFilePath)));
             BufferedOutputStream bos = new BufferedOutputStream(
                     new FileOutputStream(destFilePath))) {

            byte[] buffer = new byte[1024];
            int len;
            while ((len = gzis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        ungzip("data.txt.gz", "data_ungzipped.txt");
        System.out.println("GZIP 解压缩完成!");
    }
}

ZIP 与 GZIP 的对比

特性 ZIP GZIP
支持对象 多文件、文件夹(保留目录结构) 仅单个文件
压缩效率 适中(支持存储未压缩文件) 较高(通常比 ZIP 压缩率更高)
用途 多文件归档(如软件安装包、资源包) 单个大文件压缩(如日志、备份文件)
实现复杂度 较高(需处理条目和目录结构) 较低(直接读写文件内容)
常见扩展名 .zip .gz

最佳实践

  1. 缓冲流提升性能:压缩 / 解压缩时,使用 BufferedInputStreamBufferedOutputStream 减少 IO 次数,显著提升性能。
  2. 处理中文乱码:ZIP 操作需指定编码(如 UTF-8),避免条目名称或内容乱码。
  3. 递归压缩文件夹:ZIP 压缩文件夹时,需递归处理子文件和子目录,确保目录结构完整。
  4. 异常处理:使用 try-with-resources 自动关闭流,避免资源泄露;捕获 IO 异常并友好提示。
  5. 大文件处理:对于 GB 级大文件,可增大缓冲区大小(如 8KB 或 16KB),平衡内存占用和效率