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 模式。也可显式配置:
javazaos.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 / GzipCompressorOutputStreamBZip2CompressorInputStream / BZip2CompressorOutputStreamXZCompressorInputStream / 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 依赖)。