ApacheCommons——commons-compress(解压缩工具)

commons-compress(解压缩工具)

1、概述

commons-compress(Apache Commons Compress)是 Java 中功能最强大的压缩、解压与归档工具包。它不仅支持常用的 Zip、Gzip,还广泛支持 7z、Tar、BZip2、XZ、Pack200、Ar、Cpio 等数十种文件归档与压缩格式。

分类 常用类 (Class) 核心功能与解决问题 典型适用场景
通用工厂类 (Factory) ArchiveStreamFactory CompressorStreamFactory 通过格式名称(如 "zip", "tar", "gz")自动推断并创建对应的归档/压缩流,实现解耦。 • 支持动态格式配置的解压/打包组件 • 文件格式校验与自动流包装
Zip 归档与解压 ZipFile ZipArchiveOutputStream ZipArchiveEntry 高效支持 .zip 文件的读取、解压、修改与打包;比 Java 原生 ZipFile 拥有更好的编码与性能控制。 • 跨平台 Zip 文件解压(解决中文乱码) • 批量导出/压缩生成文件包
7z 格式支持 SevenZFile SevenZOutputFile 支持高压缩比 .7z 格式文件的读写、加密与解压处理。 • 大文件高压缩率打包与存档 • 保护性加密压缩包读写
Tar 归档 TarArchiveInputStream TarArchiveOutputStream 处理 Linux / Unix 环境下标准的 .tar 打包与解包操作(纯打包,不含压缩)。 • Docker 镜像 / Linux 安装包解包 • 结合 Gzip 构建 .tar.gz 组合流
单文件压缩流 GzipCompressorInputStream BZip2CompressorOutputStream XZCompressorInputStream 针对 .gz.bz2.xz 等单文件压缩算法的底层数据流压缩与解压。 • 日志归档压缩(.log.gz) • 大文本流传输时的网络带宽优化
xml 复制代码
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.26.1</version>
</dependency>

2、通用工厂类

commons-compress 是 Apache 提供的专用于处理归档(Archive,如 .tar, .zip, .7z)和压缩(Compressor,如 .gz, .bz2, .xz)文件的工具库。

为了避免开发者直接硬编码具体的流实现类,commons-compress 提供了两个核心的通用工厂类:

  • ArchiveStreamFactory:处理包含目录结构的多文件归档格式(ZIP, TAR, 7Z, JAR, ARJ, CPIO 等)。
  • CompressorStreamFactory:处理单文件或流的数据压缩格式(GZIP, BZIP2, XZ, LZMA, PACK200, Z 等)。

2.1、核心API

ArchiveStreamFactory(归档工厂) 用于根据名称或文件输入流自动识别并创建归档输入/输出流:

  • createArchiveInputStream(InputStream in):通过检查输入流的前几个字节(魔数 Magic Number)自动检测归档类型,并返回相应的 ArchiveInputStream。
  • createArchiveInputStream(String archiverName, InputStream in):显式指定归档格式(如 "zip", "tar")创建输入流。
  • createArchiveOutputStream(String archiverName, OutputStream out):显式指定归档格式创建输出流。

CompressorStreamFactory(压缩工厂) 用于对单个数据流进行压缩或解压缩:

  • createCompressorInputStream(InputStream in):自动检测压缩格式(如 GZIP, BZ2)并返回 CompressorInputStream。
  • createCompressorInputStream(String name, InputStream in):显式指定压缩类型创建解压流。
  • createCompressorOutputStream(String name, OutputStream out):显式指定压缩类型(如 "gz", "bzip2")创建压缩流。

2.2、使用示例

1. 通用解压/解包:自动识别格式

利用工厂类的自动检测功能,无需关心输入文件具体是 .zip 还是 .tar,一行代码即可自动匹配相应的解包流:

java 复制代码
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class AutoDetectDecompressDemo {

    public static void decompress(File archiveFile, File targetDir) throws Exception {
        try (InputStream fis = new FileInputStream(archiveFile);
             InputStream bis = new BufferedInputStream(fis);
             // 自动根据文件头魔数识别归档格式(如 ZIP, TAR 等)
             ArchiveInputStream input = new ArchiveStreamFactory().createArchiveInputStream(bis)) {

            ArchiveEntry entry;
            while ((entry = input.getNextEntry()) != null) {
                if (!input.canReadEntryData(entry)) {
                    continue;
                }

                File curFile = new File(targetDir, entry.getName());
                if (entry.isDirectory()) {
                    curFile.mkdirs();
                } else {
                    File parent = curFile.getParentFile();
                    if (!parent.exists()) {
                        parent.mkdirs();
                    }
                    try (OutputStream out = new FileOutputStream(curFile)) {
                        IOUtils.copy(input, out);
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        // 自动识别 ZIP 或 TAR 并解压
        decompress(new File("sample.zip"), new File("output_dir"));
    }
}

2. 显式指定类型打包:打包文件目录

使用 ArchiveStreamFactory 显式创建 TAR 或 ZIP 归档输出流:

java 复制代码
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;

public class ArchiveCreateDemo {

    public static void createArchive(File srcDir, File outputFile, String format) throws Exception {
        try (FileOutputStream fos = new FileOutputStream(outputFile));
             BufferedOutputStream bos = new BufferedOutputStream(fos);
             // 显式指定格式,如 ArchiveStreamFactory.ZIP 或 ArchiveStreamFactory.TAR
             ArchiveOutputStream out = new ArchiveStreamFactory().createArchiveOutputStream(format, bos)) {

            File[] files = srcDir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isFile()) {
                        // 创建归档条目
                        ArchiveEntry entry = out.createArchiveEntry(file, file.getName());
                        out.putArchiveEntry(entry);
                        
                        try (InputStream is = new FileInputStream(file)) {
                            IOUtils.copy(is, out);
                        }
                        out.closeArchiveEntry();
                    }
                }
            }
            out.finish();
        }
    }

    public static void main(String[] args) throws Exception {
        // 创建 zip 归档
        createArchive(new File("input_dir"), new File("output.zip"), ArchiveStreamFactory.ZIP);
    }
}

3. 流式压缩/解压缩 (CompressorStreamFactory)

针对单个流或文件执行高压缩率的流式处理(如 GZIP 或 BZIP2):

java 复制代码
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class CompressorDemo {

    // 1. 单文件压缩 (如将 txt 压缩为 .bz2 或 .gz)
    public static void compressFile(File inputFile, File outputFile, String compressType) throws Exception {
        try (FileInputStream fis = new FileInputStream(inputFile);
             FileOutputStream fos = new FileOutputStream(outputFile);
             BufferedOutputStream bos = new BufferedOutputStream(fos);
             // 创建压缩输出流
             CompressorOutputStream cos = new CompressorStreamFactory()
                     .createCompressorOutputStream(compressType, bos)) {
            
            IOUtils.copy(fis, cos);
        }
    }

    // 2. 自动识别压缩类型并解压
    public static void decompressFile(File compressedFile, File outputFile) throws Exception {
        try (FileInputStream fis = new FileInputStream(compressedFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             // 自动探测压缩算法 (GZIP, BZIP2, XZ 等)
             CompressorInputStream cis = new CompressorStreamFactory()
                     .createCompressorInputStream(bis);
             FileOutputStream fos = new FileOutputStream(outputFile)) {

            IOUtils.copy(cis, fos);
        }
    }

    public static void main(String[] args) throws Exception {
        File src = new File("large_log.log");
        File compressed = new File("large_log.log.bz2");
        File decompressed = new File("restored_log.log");

        // 压缩为 BZIP2 格式
        compressFile(src, compressed, CompressorStreamFactory.BZIP2);

        // 自动探测格式并解压
        decompressFile(compressed, decompressed);
    }
}

2.3、常见常量参考

ArchiveStreamFactory 和 CompressorStreamFactory 内置了格式常量的字符串表示:

  • 归档格式常量 (ArchiveStreamFactory):
    • ArchiveStreamFactory.ZIP ("zip")
    • ArchiveStreamFactory.TAR ("tar")
    • ArchiveStreamFactory.JAR ("jar")
    • ArchiveStreamFactory.SEVEN_Z ("7z") (注意:7z 需要使用专门的 SevenZFile API 进行随机读写)
    • ArchiveStreamFactory.CPIO ("cpio")
  • 压缩格式常量 (CompressorStreamFactory):
    • CompressorStreamFactory.GZIP ("gz")
    • CompressorStreamFactory.BZIP2 ("bzip2")
    • CompressorStreamFactory.XZ ("xz")
    • CompressorStreamFactory.LZMA ("lzma")
    • CompressorStreamFactory.Z ("z")

3、Zip 归档与解压

commons-compress 提供了处理 ZIP 格式的专属 API,性能与功能均优于 Java 原生的 java.util.zip。

不仅支持传统流式读写,其核心类 ZipFile 还能通过随机访问(Random Access)极大地提升大文件的解压性能,同时完美支持 ZIP 64(突破 4GB 文件与 65535 个条目限制)、中文乱码处理以及加密/压缩级别设置。

3.1、核心类

类名 核心作用 适用场景
ZipArchiveOutputStream ZIP 归档输出流 压缩并生成 ZIP 文件
ZipArchiveEntry 代表 ZIP 包内的单个文件 / 目录条目 设置压缩级别、编码、注释、文件权限等属性
ZipFile 随机读取访问 ZIP 档案 解压的首选 API,无需全包顺序扫描即可定位条目,性能极佳
ZipArchiveInputStream 纯流式读取 ZIP 档案 处理管道流、网络数据流等无法随机访问(Random Access)的输入

3.2、使用示例

1. 文件的压缩归档 (ZipArchiveOutputStream)

包含设置中文编码、压缩级别以及递归压缩目录:

java 复制代码
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.Deflater;

public class ZipCompressDemo {

    /**
     * 压缩目录或单个文件到指定 ZIP 路径
     */
    public static void compress(File srcFile, File zipFile) throws IOException {
        try (ZipArchiveOutputStream zaos = new ZipArchiveOutputStream(new FileOutputStream(zipFile))) {
            // 1. 设置编码解决中文文件名乱码问题(默认即为 UTF-8)
            zaos.setEncoding("UTF-8");

            // 2. 设置压缩级别 (BEST_COMPRESSION, BEST_SPEED, DEFAULT_COMPRESSION)
            zaos.setLevel(Deflater.BEST_COMPRESSION);

            // 3. 递归写入条目
            compressRecursive(srcFile, zaos, "");
            
            // 4. 完成打包
            zaos.finish();
        }
    }

    private static void compressRecursive(File file, ZipArchiveOutputStream zaos, String entryName) throws IOException {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File child : files) {
                    String childEntryName = entryName.isEmpty() ? child.getName() : entryName + "/" + child.getName();
                    compressRecursive(child, zaos, childEntryName);
                }
            }
        } else {
            // 创建 ZipArchiveEntry 并放入 ZIP 流
            ZipArchiveEntry entry = new ZipArchiveEntry(file, entryName);
            zaos.putArchiveEntry(entry);

            try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
                IOUtils.copy(is, zaos);
            }
            
            // 必须显式关闭当前条目
            zaos.closeArchiveEntry();
        }
    }

    public static void main(String[] args) throws IOException {
        compress(new File("my_folder"), new File("output.zip"));
    }
}

2. 高性能解压归档 (ZipFile - 推荐)

解压文件时,务必优先选用 ZipFile 而非 ZipArchiveInputStream。ZipFile 通过读取文件末尾的中央目录树实现随机访问,解析极大 ZIP 包时速度提升显著。

java 复制代码
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.utils.IOUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ZipExtractDemo {

    /**
     * 解压 ZIP 文件至目标目录
     */
    public static void decompress(File zipFile, File destDir) throws IOException {
        // 使用 ZipFile 具备随机访问能力,解压速度快
        try (ZipFile zip = new ZipFile(zipFile, "UTF-8")) {
            zip.getEntries().asIterator().forEachRemaining(entry -> {
                try {
                    extractEntry(zip, entry, destDir);
                } catch (IOException e) {
                    throw new RuntimeException("解压条目失败: " + entry.getName(), e);
                }
            });
        }
    }

    private static void extractEntry(ZipFile zip, ZipArchiveEntry entry, File destDir) throws IOException {
        // 防范 Zip Slip 跨目录注入漏洞
        File targetFile = new File(destDir, entry.getName());
        if (!targetFile.getCanonicalPath().startsWith(destDir.getCanonicalPath())) {
            throw new IOException("非法条目路径,试图超越目标目录: " + entry.getName());
        }

        if (entry.isDirectory()) {
            targetFile.mkdirs();
        } else {
            File parent = targetFile.getParentFile();
            if (!parent.exists()) {
                parent.mkdirs();
            }

            try (InputStream is = zip.getInputStream(entry);
                 OutputStream os = new FileOutputStream(targetFile)) {
                IOUtils.copy(is, os);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        decompress(new File("output.zip"), new File("extracted_folder"));
    }
}

3. 并行/多线程压缩 (ParallelScatterZipCreator)

对于包含数千个文件的极小或极大文件集合,可以使用 commons-compress 提供的并发压缩 API 提高吞吐量:

java 复制代码
import org.apache.commons.compress.archivers.zip.InputStreamSupplier;
import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.concurrent.Executors;

public class ParallelZipDemo {

    public static void parallelCompress(File[] filesToZip, File outputZip) throws Exception {
        // 创建线程池
        ParallelScatterZipCreator creator = new ParallelScatterZipCreator(Executors.newFixedThreadPool(4));

        for (File file : filesToZip) {
            ZipArchiveEntry entry = new ZipArchiveEntry(file.getName());
            entry.setMethod(ZipArchiveEntry.DEFLATED);

            InputStreamSupplier supplier = () -> {
                try {
                    return new FileInputStream(file);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            };

            // 提交给并行创建器
            creator.addArchiveEntry(entry, supplier);
        }

        // 最终合并写入目标文件
        try (ZipArchiveOutputStream zaos = new ZipArchiveOutputStream(outputZip)) {
            creator.writeTo(zaos);
        }
    }
}

3.3、高级配置与防坑指南

  • 解决 Zip Slip 安全漏洞:在解压任何压缩包时,一定要校验 targetFile.getCanonicalPath() 是否依然以解压目标路径起始(详见解压代码),防止恶意构造的 .../ 覆盖系统关键文件。

  • ZIP 64 格式支持 :默认情况下,ZipArchiveOutputStream 在需要时会自动开启 ZIP 64 模式。也可显式配置:

    java 复制代码
    zaos.setUseZip64(Zip64Mode.AsNeeded); // 可选 Always, Never, AsNeeded
  • 中文文件名:原生 JDK 的 ZipInputStream 在遇到非 UTF-8 编码的文件名时极易报错或乱码。commons-compress 内部具备编码回退机制,显式指定 new ZipFile(file, "GBK") 或 "UTF-8" 即可解决。

4、7z 格式支持

commons-compress 提供了对 7z(7-Zip)归档格式的原生支持。

由于 7z 协议本身的设计特点,7z 归档只支持随机读取与随机写入,因此它不实现通用的 ArchiveInputStream 或 ArchiveOutputStream 流式接口,而是提供了专门的工具类:

  • SevenZFile:用于读取/解压 7z 归档文件。
  • SevenZOutputFile:用于创建/压缩 7z 归档文件。

4.1、核心 API

类名 核心方法 作用 / 特点
SevenZFile SevenZFile(File file)SevenZFile(File file, char[] password)getNextEntry()read(byte[] b) / getInputStream(SevenZArchiveEntry entry) 7z 归档读取与解压 。 支持随机读取访问 7z 文件,以及解密带有密码保护的 7z 归档包。
SevenZOutputFile createArchiveEntry(File input, String entryName)putArchiveEntry(SevenZArchiveEntry entry)write(byte[] b)closeArchiveEntry()setContentCompression(SevenZMethod method) 7z 归档文件创建与压缩 。 支持设置不同的底层压缩算法(如 LZMA2、BZIP2、COPY 等)来调整压缩比与性能。
SevenZArchiveEntry getName()isDirectory()getSize()getHasCrc() 7z 包内元数据条目 。 代表 7z 归档内部的单个文件或目录,可获取文件名、文件大小、校验和等元数据。

4.2、使用示例

1. 创建与压缩 7z 文件 (SevenZOutputFile)

支持压缩单文件或递归压缩整个目录,并可灵活设置压缩算法(如推荐的 LZMA2):

java 复制代码
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZMethod;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class SevenZipCompressDemo {

    /**
     * 压缩目录或文件为 7z 格式
     */
    public static void compress(File srcFile, File sevenZFile) throws IOException {
        try (SevenZOutputFile sevenZOutput = new SevenZOutputFile(sevenZFile)) {
            // 设置压缩算法,常用 LZMA2 (高压缩率), BZIP2, COPY (不压缩)
            sevenZOutput.setContentCompression(SevenZMethod.LZMA2);

            compressRecursive(srcFile, sevenZOutput, "");
            
            // 完成写入
            sevenZOutput.finish();
        }
    }

    private static void compressRecursive(File file, SevenZOutputFile sevenZOutput, String entryName) throws IOException {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File child : files) {
                    String childEntryName = entryName.isEmpty() ? child.getName() : entryName + "/" + child.getName();
                    compressRecursive(child, sevenZOutput, childEntryName);
                }
            }
        } else {
            // 1. 创建条目并设置名称
            SevenZArchiveEntry entry = sevenZOutput.createArchiveEntry(file, entryName);
            sevenZOutput.putArchiveEntry(entry);

            // 2. 写入文件内容
            byte[] buffer = new byte[8192];
            try (InputStream is = new FileInputStream(file)) {
                int len;
                while ((len = is.read(buffer)) > 0) {
                    sevenZOutput.write(buffer, 0, len);
                }
            }

            // 3. 关闭当前条目
            sevenZOutput.closeArchiveEntry();
        }
    }

    public static void main(String[] args) throws IOException {
        compress(new File("my_data"), new File("output.7z"));
    }
}

2. 解压 7z 文件 (SevenZFile)

解压时逐个遍历 SevenZArchiveEntry 归档条目并提取文件内容,同时包含防范 Zip Slip 攻击的安全校验:

java 复制代码
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class SevenZipExtractDemo {

    /**
     * 解压 7z 文件至指定目标目录
     */
    public static void decompress(File sevenZFile, File destDir) throws IOException {
        try (SevenZFile sevenZ = new SevenZFile(sevenZFile)) {
            SevenZArchiveEntry entry;
            byte[] buffer = new byte[8192];

            while ((entry = sevenZ.getNextEntry()) != null) {
                // 防范 Zip Slip 跨目录路径注入漏洞
                File targetFile = new File(destDir, entry.getName());
                if (!targetFile.getCanonicalPath().startsWith(destDir.getCanonicalPath())) {
                    throw new IOException("非法条目路径,试图超越目标目录: " + entry.getName());
                }

                if (entry.isDirectory()) {
                    targetFile.mkdirs();
                } else {
                    File parent = targetFile.getParentFile();
                    if (!parent.exists()) {
                        parent.mkdirs();
                    }

                    // 从 SevenZFile 中读取数据并写入磁盘
                    try (OutputStream os = new FileOutputStream(targetFile)) {
                        int len;
                        while ((len = sevenZ.read(buffer)) > 0) {
                            os.write(buffer, 0, len);
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        decompress(new File("output.7z"), new File("extracted_files"));
    }
}

3. 解密带密码保护的 7z 文件

如果 7z 档案设置了解密密码,可以在创建 SevenZFile 时直接传入 char\[\] 类型的密码:

java 复制代码
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class SevenZipPasswordDemo {

    public static void decompressWithPassword(File sevenZFile, File destDir, String password) throws IOException {
        // 传入 char[] 格式的密码参数
        try (SevenZFile sevenZ = new SevenZFile(sevenZFile, password.toCharArray())) {
            SevenZArchiveEntry entry;
            byte[] buffer = new byte[8192];

            while ((entry = sevenZ.getNextEntry()) != null) {
                if (!entry.isDirectory()) {
                    File targetFile = new File(destDir, entry.getName());
                    targetFile.getParentFile().mkdirs();

                    try (OutputStream os = new FileOutputStream(targetFile)) {
                        int len;
                        while ((len = sevenZ.read(buffer)) > 0) {
                            os.write(buffer, 0, len);
                        }
                    }
                }
            }
        }
    }
}

4.3、注意事项与使用避坑

  • 不支持纯 InputStream / OutputStream 接口:7z 的文件头(Header)索引包含在档案末尾或通过复杂的块定位,因此必须直接基于 File 或 SeekableByteChannel 进行操作,不能直接作用于普通的 HTTP 流或网络 Pipe 管道。

  • 内存占用控制 :LZMA / LZMA2 解压需要占用较大的内存。若解压包含超大字典(Dictionary Size)的 7z 文件,可以通过重载构造方法控制解压的最大内存限制:

    java 复制代码
    // 限制解压最大内存占用为 100MB (100 * 1024 KB),防止 OOM
    SevenZFile sevenZ = new SevenZFile(file, null, 100 * 1024);
  • 加密限制:SevenZOutputFile 目前原生不支持直接写入带加密的 7z 档案(仅 SevenZFile 支持解密读取)。

5、Tar 归档

commons-compress 提供了专门处理 TAR 归档格式的 API。与常见的 ZIP 格式不同,TAR(Tape Archive)仅是一种文件打包/归档格式,本身不具备任何压缩功能。

在实际开发中,TAR 经常与 GZIP 或 BZIP2 结合使用(即生成 .tar.gz 或 .tar.bz2 文件)。commons-compress 提供了流式的 TarArchiveOutputStream 和 TarArchiveInputStream,可以与压缩流无缝组合。

5.1、核心API

类名 核心方法 作用 / 特点
TarArchiveOutputStream setLongFileMode(...)setBigNumberMode(...)putArchiveEntry(entry)closeArchiveEntry() TAR 归档输出流 。 用于将文件或目录写入 TAR 打包流;支持设置超长文件名处理模式(如 GNU / POSIX 扩展)以及大文件(> 8GB)超限模式。
TarArchiveInputStream getNextEntry() / getNextTarEntry()canReadEntryData(entry) TAR 归档输入流 。 从 TAR 流中按顺序逐个提取并读取条目,判断条目数据是否可读。
TarArchiveEntry setModTime(...)setUserId(...) / setGroupId(...)setMode(...) TAR 包内元数据条目 。 代表 TAR 中的单个文件或目录条目;完整保留 Linux/Unix 原生的文件权限(POSIX Mode)、用户/组 ID 及最后修改时间。

5.2、使用示例

1. 打包生成 .tar 文件 (TarArchiveOutputStream)

实现将目录递归打包为标准的 .tar 文件,保留文件权限和修改时间:

java 复制代码
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class TarPackDemo {

    public static void packToTar(File srcDir, File targetTarFile) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(targetTarFile);
             TarArchiveOutputStream taos = new TarArchiveOutputStream(fos)) {

            // 必须设置:处理长文件名和大文件,避免超长路径报错
            taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
            taos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);

            // 递归写入条目
            packRecursive(srcDir, taos, "");

            // 完成归档写入
            taos.finish();
        }
    }

    private static void packRecursive(File file, TarArchiveOutputStream taos, String entryName) throws IOException {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File child : files) {
                    String childEntryName = entryName.isEmpty() ? child.getName() : entryName + "/" + child.getName();
                    packRecursive(child, taos, childEntryName);
                }
            }
        } else {
            // 创建 TarArchiveEntry,会自动带上文件修改时间和权限
            TarArchiveEntry entry = new TarArchiveEntry(file, entryName);
            taos.putArchiveEntry(entry);

            try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
                IOUtils.copy(is, taos);
            }

            // 必须显式关闭当前条目
            taos.closeArchiveEntry();
        }
    }

    public static void main(String[] args) throws IOException {
        packToTar(new File("my_folder"), new File("output.tar"));
    }
}

2. 解包 .tar 文件 (TarArchiveInputStream)

流式解包 .tar 文件,同时防范 Zip/Tar Slip 漏洞:

java 复制代码
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class TarUnpackDemo {

    public static void unpackTar(File tarFile, File destDir) throws IOException {
        try (InputStream fis = new FileInputStream(tarFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             TarArchiveInputStream tais = new TarArchiveInputStream(bis)) {

            TarArchiveEntry entry;
            while ((entry = tais.getNextTarEntry()) != null) {
                // 防范 Zip/Tar Slip 路径注入漏洞
                File targetFile = new File(destDir, entry.getName());
                if (!targetFile.getCanonicalPath().startsWith(destDir.getCanonicalPath())) {
                    throw new IOException("非法条目路径,试图超越目标目录: " + entry.getName());
                }

                if (entry.isDirectory()) {
                    targetFile.mkdirs();
                } else {
                    File parent = targetFile.getParentFile();
                    if (!parent.exists()) {
                        parent.mkdirs();
                    }

                    try (OutputStream os = new FileOutputStream(targetFile)) {
                        IOUtils.copy(tais, os);
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        unpackTar(new File("output.tar"), new File("extracted_folder"));
    }
}

3. 实战首选:组合解压 .tar.gz 格式

将 GzipCompressorInputStream 与 TarArchiveInputStream 嵌套包装,即可一步到位解压标准的 .tar.gz 文件:

java 复制代码
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class TarGzDemo {

    /**
     * 一键解压 .tar.gz 文件
     */
    public static void decompressTarGz(File tarGzFile, File destDir) throws IOException {
        try (InputStream fis = new FileInputStream(tarGzFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             // 1. 解压 GZIP 压缩流
             GzipCompressorInputStream gzis = new GzipCompressorInputStream(bis);
             // 2. 解包 TAR 归档流
             TarArchiveInputStream tais = new TarArchiveInputStream(gzis)) {

            TarArchiveEntry entry;
            while ((entry = tais.getNextTarEntry()) != null) {
                File targetFile = new File(destDir, entry.getName());

                // 安全路径校验
                if (!targetFile.getCanonicalPath().startsWith(destDir.getCanonicalPath())) {
                    throw new IOException("安全警告: 非法解压路径 " + entry.getName());
                }

                if (entry.isDirectory()) {
                    targetFile.mkdirs();
                } else {
                    targetFile.getParentFile().mkdirs();
                    try (OutputStream os = new FileOutputStream(targetFile)) {
                        IOUtils.copy(tais, os);
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        decompressTarGz(new File("archive.tar.gz"), new File("decompressed_out"));
    }
}

6、单文件压缩流

commons-compress 专门提供了处理单文件/纯数据流压缩的 API。与 TAR、ZIP 等负责处理目录结构的归档(Archive)工具不同,压缩流(Compressor)只负责对单一数据流进行数据压缩与解压缩,不包含文件名、文件层级或目录结构信息。

常用的单文件压缩格式包括:GZIP (.gz)、BZIP2 (.bz2)、XZ (.xz)、LZMA (.lzma)、Zstandard (.zst) 等。

6.1、核心 API

所有单文件压缩格式均遵循统一的抽象设计:

  • 基类接口:
    • CompressorInputStream:所有解压输入流的抽象基类。
    • CompressorOutputStream:所有压缩输出流的抽象基类。
  • 通用工厂类:
    • CompressorStreamFactory:核心通用工厂。
      • createCompressorInputStream(InputStream in):自动识别流的压缩魔数(Magic Number)并返回对应的解压流。
      • createCompressorOutputStream(String name, OutputStream out):根据格式名称创建压缩输出流。
  • 特定格式专属类(当需要配置高级参数,如压缩级别或词典大小):
    • GzipCompressorInputStream / GzipCompressorOutputStream
    • BZip2CompressorInputStream / BZip2CompressorOutputStream
    • XZCompressorInputStream / XZCompressorOutputStream

核心格式特点对比

格式名称 常量标示名 压缩速度 压缩率 适用场景
GZIP CompressorStreamFactory.GZIP ("gz") 极快 中等 • 日志文件实时压缩 • HTTP 网络传输(Content-Encoding: gzip) • 基础数据打包
BZIP2 CompressorStreamFactory.BZIP2 ("bzip2") 较慢 较高 • 文本与日志的高倍率离线归档备份
XZ / LZMA CompressorStreamFactory.XZ ("xz") 极高 • Linux 软件包分发(如 .tar.xz) • 存储空间极度敏感、读多写少的大文件存档
Zstandard (zstd) CompressorStreamFactory.ZSTANDARD ("zst") 极快 极高 现代高并发/大数据首选 • 兼顾极致的压缩/解压速度与超高压缩率

6.2、使用示例

1. 基于工厂类的通用文件压缩与解压(支持自动识别)

使用 CompressorStreamFactory 可以屏蔽具体算法的差异:

java 复制代码
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class UniversalCompressorDemo {

    /**
     * 将单文件压缩为指定格式 (.gz, .bz2, .xz 等)
     */
    public static void compressFile(File srcFile, File destFile, String compressType) throws Exception {
        try (FileInputStream fis = new FileInputStream(srcFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             FileOutputStream fos = new FileOutputStream(destFile);
             BufferedOutputStream bos = new BufferedOutputStream(fos);
             // 1. 工厂模式:根据类型名称创建对应的压缩输出流
             CompressorOutputStream cos = new CompressorStreamFactory()
                     .createCompressorOutputStream(compressType, bos)) {

            IOUtils.copy(bis, cos);
        }
    }

    /**
     * 自动探测压缩格式并解压
     */
    public static void decompressFile(File compressedFile, File destFile) throws Exception {
        try (FileInputStream fis = new FileInputStream(compressedFile);
             // 必须使用 BufferedInputStream 支持 mark/reset 以供工厂类读取头几个字节进行魔数校验
             BufferedInputStream bis = new BufferedInputStream(fis);
             // 2. 工厂模式:自动检测魔数并返回正确的解压流 (如 GzipCompressorInputStream)
             CompressorInputStream cis = new CompressorStreamFactory()
                     .createCompressorInputStream(bis);
             FileOutputStream fos = new FileOutputStream(destFile)) {

            IOUtils.copy(cis, fos);
        }
    }

    public static void main(String[] args) throws Exception {
        File rawLog = new File("app_server.log");
        File gzLog = new File("app_server.log.gz");
        File restoredLog = new File("restored_app_server.log");

        // 1. 压缩为 GZIP
        compressFile(rawLog, gzLog, CompressorStreamFactory.GZIP);

        // 2. 自动检测格式并解压
        decompressFile(gzLog, restoredLog);
    }
}

2. GZIP 高级 API 与参数配置 (GzipCompressorOutputStream)

如果需要调整具体的压缩级别(如最高压缩比 BEST_COMPRESSION 或最高速度 BEST_SPEED)或添加文件元数据(如文件名与修改时间),可以直接使用具体的格式类:

java 复制代码
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipParameters;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.Deflater;

public class GzipAdvancedDemo {

    public static void compressWithGzipParams(File srcFile, File gzipFile) throws Exception {
        // 1. 配置 GZIP 参数
        GzipParameters parameters = new GzipParameters();
        parameters.setCompressionLevel(Deflater.BEST_COMPRESSION); // 设置最高压缩级别
        parameters.setFilename(srcFile.getName());                 // 写入原始文件名头信息
        parameters.setModificationTime(srcFile.lastModified());     // 写入原始修改时间

        try (FileInputStream fis = new FileInputStream(srcFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             FileOutputStream fos = new FileOutputStream(gzipFile);
             // 2. 传入配置创建 GzipCompressorOutputStream
             GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos, parameters)) {

            IOUtils.copy(bis, gzos);
        }
    }
}

3. 高压缩比 XZ 格式应用示例 (XZCompressorOutputStream)

XZ 算法提供极高的压缩率(通常显著高于 GZIP 和 BZIP2):

java 复制代码
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class XzCompressDemo {

    public static void compressToXz(File srcFile, File xzFile) throws Exception {
        try (FileInputStream fis = new FileInputStream(srcFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             FileOutputStream fos = new FileOutputStream(xzFile);
             // 可以通过构造方法指定 preset (0-9),数字越大压缩率越高但越耗 CPU 和内存
             XZCompressorOutputStream xzos = new XZCompressorOutputStream(fos, 6)) {

            IOUtils.copy(bis, xzos);
        }
    }

    public static void decompressXz(File xzFile, File destFile) throws Exception {
        try (FileInputStream fis = new FileInputStream(xzFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             XZCompressorInputStream xzis = new XZCompressorInputStream(bis);
             FileOutputStream fos = new FileOutputStream(destFile)) {

            IOUtils.copy(xzis, fos);
        }
    }
}

关键注意事项

  • 缓存流必不可少:在调用 CompressorStreamFactory.createCompressorInputStream(InputStream) 进行自动类型推断时,传入的 InputStream 必须支持 mark() 和 reset() 机制(例如包装为 BufferedInputStream),否则工厂读取格式魔数后无法重置流,导致抛出 IllegalArgumentException 或解压损坏。
  • 底层算法依赖:部分格式(如 XZ、Zstandard)需要引入对应的底层依赖才能正常工作(见下方 Maven 依赖)。
相关推荐
程序员清风1 小时前
聊聊怎么缓解找工作的焦虑感?
java·后端·面试
sunshine22 girl1 小时前
Java学习一 环境配置3 Idea的基本设置和插件
java·学习
会编程的吕洞宾1 小时前
LangChain4j RAG 分块策略实战:检索质量提升 80% 的 Chunking 调优全解
java·人工智能·后端
Sagittarius_A*1 小时前
【LitCTF2026】lit_ezsql
android·java·数据库
LayZhangStrive2 小时前
数据结构与算法 - 堆排序
java·数据结构·算法·排序算法·堆排序·大根堆
是2的10次方啊2 小时前
线程 Dump 还是 Heap Dump?线上故障时先抓哪个
java
Zane19942 小时前
复制算法明明要浪费一半内存,为什么新生代还偏偏用它
java·后端
worilb2 小时前
Java/JVM 常见诊断文件对比
java·开发语言
toolsmith2 小时前
AI能替你读代码,但替代不了你积累洞察力:一次YouTrack逆向实战全记录
java·源码阅读