1. 项目概述
本文将介绍如何使用 Apache Commons Compress 库进行文件压缩、归档和解压操作。我们将探讨其支持的格式、核心功能以及使用时的注意事项。
2. Apache Commons Compress 是什么
Apache Commons Compress 是一个为常用压缩和归档格式提供统一接口的库。它不仅支持 TAR、ZIP、GZIP 等主流格式,还涵盖 BZIP2、XZ、LZMA、Snappy 等专业格式。
2.1 压缩器与归档器的区别
归档器(如 TAR)将目录结构打包成单个文件,而压缩器则通过算法减少数据体积。某些格式(如 ZIP)兼具归档和压缩功能,但在该库中被归类为归档器。
可通过以下方式查看支持的格式:
- 归档格式:查看
ArchiveStreamFactory
类的静态字段 - 压缩格式:查看
CompressorStreamFactory
类的静态字段
2.2 依赖配置
首先添加核心依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.26.1</version>
</dependency>
默认支持 TAR、ZIP、BZIP2、CPIO 和 GZIP。其他格式需额外依赖:
<!-- 支持 XZ、7z、LZMA -->
<dependency>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<version>1.9</version>
</dependency>
<!-- 支持 LZ4 和 ZSTD -->
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.5.5-11</version>
</dependency>
⚠️ 缺少这些依赖会导致处理特定格式时抛出异常。
3. 流的压缩与解压
虽然库为不同格式提供了通用抽象,但各格式仍有独特功能。我们重点使用 CompressorStreamFactory
实现格式无关的代码,而非直接操作具体实现类(如 GzipCompressorInputStream
)。
3.1 压缩文件
压缩时需指定格式。通过 FileNameUtils
获取文件扩展名作为格式参数:
public class CompressUtils {
public static void compressFile(Path file, Path destination) {
String format = FileNameUtils.getExtension(destination);
try (OutputStream out = Files.newOutputStream(destination);
BufferedOutputStream buffer = new BufferedOutputStream(out);
CompressorOutputStream compressor = new CompressorStreamFactory()
.createCompressorOutputStream(format, buffer)) {
IOUtils.copy(Files.newInputStream(file), compressor);
}
}
// ...
}
测试用例:
@Test
void givenFile_whenCompressing_thenCompressed() {
Path destination = Paths.get("/tmp/simple.txt.gz");
CompressUtils.compressFile(Paths.get("/tmp/simple.txt"), destination);
assertTrue(Files.isRegularFile(destination));
}
通过修改目标文件扩展名即可切换压缩格式(如 .bz2
、.xz
),且支持任意文件类型作为输入。
3.2 解压文件
解压时库会自动检测格式:
public static void decompress(Path file, Path destination) {
try (InputStream in = Files.newInputStream(file);
BufferedInputStream inputBuffer = new BufferedInputStream(in);
OutputStream out = Files.newOutputStream(destination);
CompressorInputStream decompressor = new CompressorStreamFactory()
.createCompressorInputStream(inputBuffer)) {
IOUtils.copy(decompressor, out);
}
}
测试用例:
@Test
void givenCompressedArchive_whenDecompressing_thenArchiveAvailable() {
Path destination = Paths.get("/tmp/decompressed-archive.tar");
CompressUtils.decompress("/tmp/archive.tar.gz", destination);
assertTrue(Files.isRegularFile(destination));
}
✅ 支持任意压缩格式组合(如 .tar.xz
、.zip.gz
),且不限于归档文件。
4. 创建与操作归档
4.1 创建归档
使用 Archiver
类的便捷方法归档整个目录:
public static void archive(Path directory, Path destination) {
String format = FileNameUtils.getExtension(destination);
new Archiver().create(format, destination, directory);
}
4.2 归档与压缩结合
单步操作创建压缩归档:将扩展名拆分为压缩格式和归档格式:
public static void archiveAndCompress(Path directory, Path destination) {
String compressionFormat = FileNameUtils.getExtension(destination);
String archiveFormat = FilenameUtils.getExtension(
destination.getFileName().toString().replace("." + compressionFormat, ""));
try (OutputStream archive = Files.newOutputStream(destination);
BufferedOutputStream archiveBuffer = new BufferedOutputStream(archive);
CompressorOutputStream compressor = new CompressorStreamFactory()
.createCompressorOutputStream(compressionFormat, archiveBuffer);
ArchiveOutputStream<?> archiver = new ArchiveStreamFactory()
.createArchiveOutputStream(archiveFormat, compressor)) {
new Archiver().create(archiver, directory);
}
}
4.3 解压归档
使用 Expander
类单行解压:
public static void extract(Path archive, Path destination) {
new Expander().expand(archive, destination);
}
自动处理流操作、格式检测和文件提取。
4.4 提取单个条目
遍历归档条目并匹配文件名:
public static void extractOne(Path archivePath, String fileName, Path destinationDirectory) {
try (InputStream input = Files.newInputStream(archivePath);
BufferedInputStream buffer = new BufferedInputStream(input);
ArchiveInputStream<?> archive = new ArchiveStreamFactory()
.createArchiveInputStream(buffer)) {
ArchiveEntry entry;
while ((entry = archive.getNextEntry()) != null) {
if (entry.getName().equals(fileName)) {
Path outFile = destinationDirectory.resolve(fileName);
Files.createDirectories(outFile.getParent());
try (OutputStream os = Files.newOutputStream(outFile)) {
IOUtils.copy(archive, os);
}
break;
}
}
}
}
测试用例:
@Test
void givenExistingArchive_whenExtractingSingleEntry_thenFileExtracted() {
Path archive = Paths.get("/tmp/archive.tar.gz");
String targetFile = "sub-directory/some.txt";
CompressUtils.extractOne(archive, targetFile, Paths.get("/tmp/"));
assertTrue(Files.isRegularFile("/tmp/sub-directory/some.txt"));
}
⚠️ 文件名可包含归档内的子目录路径。
4.5 添加条目到现有归档
库不直接支持追加条目。踩坑点:直接调用 putArchiveEntry()
会覆盖内容。替代方案:
@Test
void givenExistingArchive_whenAddingSingleEntry_thenArchiveModified() {
Path archive = Paths.get("/tmp/archive.tar");
Path newArchive = Paths.get("/tmp/modified-archive.tar");
Path tmpDir = Paths.get("/tmp/extracted-archive");
Path newEntry = Paths.get("/tmp/new-entry.txt");
// 1. 解压归档
CompressUtils.extract(archive, tmpDir);
assertTrue(Files.isDirectory(tmpDir));
// 2. 添加新文件
Files.copy(newEntry, tmpDir.resolve(newEntry.getFileName()));
// 3. 重新归档
CompressUtils.archive(tmpDir, newArchive);
assertTrue(Files.isRegularFile(newArchive));
// 4. 清理并替换
FileUtils.deleteDirectory(tmpDir.toFile());
Files.delete(archive);
Files.move(newArchive, archive);
assertTrue(Files.isRegularFile(archive));
}
⚠️ 此操作会破坏原归档,建议先备份。
4.6 直接使用具体实现
需要格式独有功能时(如 ZIP 压缩级别),直接操作具体类:
public static void zip(Path file, Path destination) {
try (InputStream input = Files.newInputStream(file);
OutputStream output = Files.newOutputStream(destination);
ZipArchiveOutputStream archive = new ZipArchiveOutputStream(output)) {
archive.setMethod(ZipEntry.DEFLATED);
archive.setLevel(Deflater.BEST_COMPRESSION);
archive.putArchiveEntry(new ZipArchiveEntry(file.getFileName().toString()));
IOUtils.copy(input, archive);
archive.closeArchiveEntry();
}
}
5. 局限性
使用时需注意以下限制:
- 多卷归档支持有限:处理分卷归档时需谨慎
- 编码问题:跨文件系统或非标准化数据时可能出现编码异常
- ZIP 处理建议:Apache 官方建议在复杂场景使用
ZipFile
获取更多控制 - TAR 格式注意事项:参考 TAR 专用文档
6. 总结
Apache Commons Compress 提供了强大的文件压缩与归档能力。通过理解其特性与限制,我们能以格式无关的方式高效处理文件操作。
完整示例代码见 GitHub 仓库。