Apache Commons Compress 常用 CLI 命令与工具详解
实测 Commons Compress 1.28.0 命令行用法:发行包 jar 的 Lister 主类、java -cp 启动、jshell 交互实操、javac 手动运行示例,并对照系统 tar、gzip、xz、bzip2 命令。
Apache Commons Compress 常用 CLI 命令与工具详解
目标:搞清楚 Commons Compress 1.28.0 到底能怎么「在命令行用」——发行包 jar 自带的 Lister 入口、jshell 交互实验、javac/java 手动运行示例程序,并与 tar/gzip 等系统命令做对照。全部命令与回显已在 Ubuntu 22.04 / CentOS 8(JDK 17)实测通过。
一、环境信息
本章所有命令行操作基于以下环境。Commons Compress 是 jar 库,无常驻服务,其「命令行能力」只有三种形态:jar 清单声明的 Lister 主类、jshell 交互环境、以及手写的演示程序。
java -version 2>&1 | head -1
tar --version | head -1
gzip --version | head -1
bzip2 --version 2>&1 | head -1
xz --version | head -1
实测回显(Ubuntu 22.04):
openjdk version "17.0.20.1" 2026-08-18
tar (GNU tar) 1.34
gzip 1.10
bzip2, a block-sorting file compressor. Version 1.0.8, 13-Jul-2019.
xz (XZ Utils) 5.2.5
准备实验文件与发行包(手动安装方式,依赖 jar 缺一不可,原因见第二章):
sudo mkdir -p /opt/compress-lab/lib
cd /opt/compress-lab
sudo curl -fsSLO https://downloads.apache.org/commons/compress/binaries/commons-compress-1.28.0-bin.tar.gz
sudo curl -fsSLO https://downloads.apache.org/commons/compress/binaries/commons-compress-1.28.0-bin.tar.gz.sha512
printf '%s %s\n' "$(cat commons-compress-1.28.0-bin.tar.gz.sha512)" commons-compress-1.28.0-bin.tar.gz | sha512sum -c -
sudo tar xzf commons-compress-1.28.0-bin.tar.gz
cd lib && sudo curl -fsSLO https://repo1.maven.org/maven2/commons-io/commons-io/2.20.0/commons-io-2.20.0.jar && sudo curl -fsSLO https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.18.0/commons-lang3-3.18.0.jar && sudo curl -fsSLO https://repo1.maven.org/maven2/commons-codec/commons-codec/1.19.0/commons-codec-1.19.0.jar && cd /opt/compress-lab
mkdir -p demo/data && echo "hello commons compress" > demo/data/a.txt
tar -cf demo/sample.tar -C demo/data a.txt
实测回显(校验一步):
commons-compress-1.28.0-bin.tar.gz: OK
💡 技术拓展:命令行处理压缩文件前要先分清两类工具的边界。**归档(archiver)**把多文件打包成一个文件(tar/zip/jar),**压缩(compressor)**只处理单条数据流(gzip/bzip2/xz);
tar.gz是「先归档再压缩」的两层结构,需要两层工具配合。对比下表:
| 类别 | 格式 | 是否压缩 | 随机访问 | 典型工具 |
|---|---|---|---|---|
| 归档 | tar | 否(仅打包) | 否 | GNU tar、Commons Compress |
| 归档 | zip | 是(每条目 DEFLATE) | 是(中央目录) | Info-ZIP、Commons Compress |
| 归档 | jar | 是(zip 超集) | 是 | JDK jar、Commons Compress |
| 归档 | 7z | 是 | 部分 | p7zip、Commons Compress(只读为主) |
| 压缩 | gzip | 是(DEFLATE) | 否 | gzip、Commons Compress |
| 压缩 | bzip2 | 是(BWT) | 否 | bzip2、Commons Compress |
| 压缩 | xz | 是(LZMA2) | 否 | XZ Utils、Commons Compress |
| 压缩 | zstd | 是(Zstandard) | 否 | zstd、Commons Compress(需 zstd-jni) |
本章既讲 Commons Compress 自己的命令行入口,也把它与上表中的系统命令逐一对照。
💡 技术拓展:Java 生态里能做压缩的库不止一家,选库前可参考下表,本文的主角是覆盖面最广的 Commons Compress:
| 库 | 坐标 | 支持格式 | 特点 |
|---|---|---|---|
| JDK 内置 | 无(java.util.zip) | zip/gzip/deflate | 零依赖,但不支持 tar、7z、bzip2、xz 等 |
| Commons Compress | org.apache.commons:commons-compress | 14 种压缩流、8 种归档流 | 覆盖面最广,API 统一 |
| snappy-java | org.xerial.snappy:snappy-java | snappy | 极致速度,JNI 实现,无归档能力 |
| XZ for Java | org.tukaani:xz | xz/lzma | 高压缩比,Commons Compress 以它为可选依赖 |
二、发行包自带的命令行入口:Lister
先回答一个问题:commons-compress-1.28.0.jar 能不能像 tar 一样当命令行工具用?答案藏在 jar 清单里。解出清单查看:
cd /opt/compress-lab/commons-compress-1.28.0
jar xf commons-compress-1.28.0.jar META-INF/MANIFEST.MF
grep -E "Main-Class|X-Compile" META-INF/MANIFEST.MF
实测回显:
Main-Class: org.apache.commons.compress.archivers.Lister
X-Compile-Source-JDK: 1.8
X-Compile-Target-JDK: 1.8
清单声明了 Main-Class 为 org.apache.commons.compress.archivers.Lister——一个「列出归档内容」的工具类,同时确认字节码目标是 Java 8。但直接 java -jar 会失败:
cd /opt/compress-lab
java -jar commons-compress-1.28.0/commons-compress-1.28.0.jar
echo "退出码: $?"
实测回显:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/ArrayUtils
at org.apache.commons.compress.archivers.Lister.main(Lister.java:80)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.ArrayUtils
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
(输出节选,实测首尾,省略了最后一条调用帧;退出码 1)
原因:1.28.0 有三个必需依赖(commons-lang3、commons-io、commons-codec),而 java -jar 模式会忽略 -cp。正确的命令行形态是 -cp 带上依赖、用全限定类名启动:
java -cp "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" org.apache.commons.compress.archivers.Lister demo/sample.tar
实测回显:
Analyzing demo/sample.tar
Detected format tar
Created org.apache.commons.compress.archivers.tar.TarFile@1698c449
a.txt
四行输出依次是:分析对象、自动识别的格式、内部创建的随机访问类(tar 用 TarFile)、条目列表。对 zip 同理(内部用 ZipFile):
cd demo/data && zip -q ../sample.zip a.txt && cd /opt/compress-lab
java -cp "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" org.apache.commons.compress.archivers.Lister demo/sample.zip
实测回显:
Analyzing demo/sample.zip
Detected format zip
Created org.apache.commons.compress.archivers.zip.ZipFile@1c4af82c
a.txt
不带参数运行可以看到它的用法说明:
java -cp "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" org.apache.commons.compress.archivers.Lister
实测回显:
Parameters: archive-name [archive-type]
The magic archive-type 'zipfile' prefers ZipFile over ZipArchiveInputStream
The magic archive-type 'tarfile' prefers TarFile over TarArchiveInputStream
第二参数可指定类型(如 zip、tar),帮助里提到的 zipfile、tarfile 两个魔法值实测在 1.28.0 上会报错,属已知行为,指定标准类型名即可:
java -cp "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" org.apache.commons.compress.archivers.Lister demo/sample.zip zip
java -cp "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" org.apache.commons.compress.archivers.Lister demo/sample.zip zipfile
实测回显:
Analyzing demo/sample.zip
Detected format zip
Created org.apache.commons.compress.archivers.zip.ZipFile@6996db8
a.txt
Analyzing demo/sample.zip
Detected format zipfile
Exception in thread "main" org.apache.commons.compress.archivers.ArchiveException: Archiver: zipfile not found.
at org.apache.commons.compress.archivers.ArchiveStreamFactory.createArchiveInputStream(ArchiveStreamFactory.java:477)
at org.apache.commons.compress.archivers.ArchiveStreamFactory.createArchiveInputStream(ArchiveStreamFactory.java:418)
最后看 Lister 的边界:它只识别归档签名,tar.gz 这类外层压缩文件不处理:
cd /opt/compress-lab/demo/data && tar -czf ../sample.tar.gz a.txt && cd /opt/compress-lab
java -cp "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" org.apache.commons.compress.archivers.Lister demo/sample.tar.gz
实测回显:
Analyzing demo/sample.tar.gz
Exception in thread "main" org.apache.commons.compress.archivers.ArchiveException: No Archiver found for the stream signature
at org.apache.commons.compress.archivers.ArchiveStreamFactory.detect(ArchiveStreamFactory.java:289)
at org.apache.commons.compress.archivers.Lister.detectFormat(Lister.java:62)
at org.apache.commons.compress.archivers.Lister.list(Lister.java:122)
at org.apache.commons.compress.archivers.Lister.go(Lister.java:114)
at org.apache.commons.compress.archivers.Lister.main(Lister.java:84)
结论:Lister 只能「列纯归档」,不能解压、不能压缩、不能穿透 gzip/bzip2 外层。它更像 API 的自检工具,替代不了 tar/zip 命令。
2.1 jshell 交互实操
jshell 适合把库当「命令行」来探索。把实验语句存成脚本(jshell 脚本模式不会自动回显表达式值,必须显式 System.out.println):
cat > /opt/compress-lab/hello-compress.jsh <<'EOF'
import org.apache.commons.compress.compressors.*;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipParameters;
import java.io.*;
byte[] data = "jshell + commons-compress 命令行实验".getBytes("UTF-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GzipParameters params = new GzipParameters();
params.setFileName("note.txt");
params.setCompressionLevel(java.util.zip.Deflater.BEST_COMPRESSION);
try (GzipCompressorOutputStream gz = new GzipCompressorOutputStream(bos, params)) { gz.write(data); }
System.out.println("gzip 输出大小: " + bos.size() + " 字节");
byte[] raw = bos.toByteArray();
ByteArrayInputStream in = new ByteArrayInputStream(raw);
System.out.println("探测格式: " + CompressorStreamFactory.detect(in));
CompressorInputStream cis = new CompressorStreamFactory().createCompressorInputStream(new ByteArrayInputStream(raw));
byte[] restored = cis.readAllBytes();
cis.close();
System.out.println("解压还原: " + new String(restored, "UTF-8"));
/exit
EOF
该命令无输出。带类路径启动 jshell 执行脚本:
cd /opt/compress-lab
jshell --class-path "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" hello-compress.jsh
实测回显:
gzip 输出大小: 70 字节
探测格式: gz
解压还原: jshell + commons-compress 命令行实验
三个实测确认的 jshell 使用要点:导入子包要写全(compressors.* 不含 compressors.gzip.*,实测漏写会报 cannot find symbol: class GzipCompressorOutputStream);脚本文件模式不回显表达式值(实测 1+1 语句在脚本中无任何输出),交互式会话才会;/exit 结束会话。
2.2 javac + java -cp 手动运行示例程序
没有构建工具时,javac/java -cp 是运行库代码的标准方式。写一个最小验证程序 /opt/compress-lab/HelloCompress.java(去掉 package 行后保存):
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
public class HelloCompress {
public static void main(String[] args) throws IOException {
byte[] data = "hello commons compress".getBytes(StandardCharsets.UTF_8);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(bos)) {
ZipArchiveEntry entry = new ZipArchiveEntry("hello.txt");
entry.setSize(data.length);
zos.putArchiveEntry(entry);
zos.write(data);
zos.closeArchiveEntry();
}
File tmp = File.createTempFile("hello", ".zip");
Files.write(tmp.toPath(), bos.toByteArray());
try (ZipFile zf = ZipFile.builder().setFile(tmp).setCharset("UTF-8").get();
BufferedReader reader = new BufferedReader(
new InputStreamReader(zf.getInputStream(zf.getEntry("hello.txt")), StandardCharsets.UTF_8))) {
System.out.println("zip 读回内容: " + reader.readLine());
} finally {
tmp.delete();
}
Package p = ZipArchiveEntry.class.getPackage();
System.out.println("commons-compress 版本: " + p.getImplementationVersion());
System.out.println("验证通过");
}
}
编译并运行:
cd /opt/compress-lab
javac -encoding UTF-8 -cp "commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" HelloCompress.java
java -Dfile.encoding=UTF-8 -cp ".:commons-compress-1.28.0/commons-compress-1.28.0.jar:lib/*" HelloCompress
实测回显:
zip 读回内容: hello commons compress
commons-compress 版本: 1.28.0
验证通过
类路径写法要点:Linux 分隔符是冒号;. 让 JVM 找到当前目录的 HelloCompress.class;lib/* 由 JVM 展开,不要让 shell 先展开。
三、与系统命令对照
Commons Compress 能在命令行做的事有限,本节把它与 tar/gzip/xz/bzip2 系统命令逐项对照,帮读者判断什么时候该用谁。功能对照:
| 能力 | 系统命令 | Commons Compress 命令行形态 |
|---|---|---|
| 列出 tar 内容 | tar -tf x.tar | Lister(-cp 启动,仅纯归档) |
| 列出 zip 内容 | unzip -l x.zip | Lister |
| 解包 tar | tar -xf x.tar | 无(需写 Java 程序) |
| 压缩单文件 | gzip x.txt | 无(需写 Java 程序) |
| 解压 tar.gz | tar -xzf x.tar.gz | 无(Lister 不识别压缩层) |
| 创建归档 | tar -cf / zip | 无(需写 Java 程序) |
| 自动探测格式 | 无 | Lister 自动识别 + 库内 detect API |
| 脚本化复杂流程 | shell 管道 | jshell 脚本 / Java 程序 |
性能对照:用一段 10,485,760 字节的重复文本实测四个系统命令(Ubuntu 22.04,time 实测):
yes "apache commons compress benchmark line 00001234 abcdefgh" | head -c 10M > bench.txt
time gzip -k -6 bench.txt
ls -l bench.txt.gz
实测回显:
real 0m0.071s
user 0m0.069s
sys 0m0.004s
-rw-r--r-- 1 root root 30594 Oct 13 08:23 bench.txt.gz
同样流程换 xz 与 bzip2(每次先删除上一次输出文件,-k 不会覆盖同名输出):
rm -f bench.txt.gz
time xz -k -6 bench.txt
ls -l bench.txt.xz
rm -f bench.txt.xz
time bzip2 -k -9 bench.txt
ls -l bench.txt.bz2
rm -f bench.txt bench.txt.bz2
实测回显:
real 0m0.253s
user 0m0.201s
sys 0m0.055s
-rw-r--r-- 1 root root 1712 Oct 13 08:23 bench.txt.xz
real 0m3.140s
user 0m3.132s
sys 0m0.010s
-rw-r--r-- 1 root root 2669 Oct 13 08:23 bench.txt.bz2
汇总成对照表(同一 10MB 输入,Ubuntu 22.04 实测):
| 命令 | 耗时 | 输出大小 | 说明 |
|---|---|---|---|
| gzip -6 | 0.071s | 30,594 B | 最快,兼容性最好 |
| xz -6 | 0.253s | 1,712 B | 压缩比最高档 |
| bzip2 -9 | 3.140s | 2,669 B | 比xz慢一个数量级,压缩比不如xz |
库内同一份数据的对比(CompStreamDemo,1,348,890 字节输入实测):
gzip 级别1: 55677 字节
gzip 级别9: 52677 字节
bzip2 : 22000 字节
xz : 26692 字节
lzma : 26615 字节
deflate(9): 52620 字节
两侧结论一致:deflate 系(gzip/zip)最快但压缩比最低,xz/lzma 压缩比最高,bzip2 速度与压缩比都居中偏慢。选型建议:shell 交互与运维脚本用系统命令(零 Java 环境、有管道与 -k 等成熟参数);Java 程序内部处理归档/压缩用 Commons Compress(跨平台、可嵌入业务逻辑、支持 22 种格式);两者不要混用在同一条流水线里做半成品转换。
四、常见问题
Q1:java -jar commons-compress-1.28.0.jar 报 NoClassDefFoundError?
发行包 jar 虽有 Main-Class: org.apache.commons.compress.archivers.Lister,但 1.28.0 必需依赖 commons-lang3/commons-io/commons-codec,java -jar 会忽略 -cp。改用 java -cp "主jar:lib/*" org.apache.commons.compress.archivers.Lister 文件,见第二章实测。
Q2:Lister 能解压 tar.gz 吗?
不能。实测对 sample.tar.gz 报 ArchiveException: No Archiver found for the stream signature(Lister 只探测归档签名,不处理压缩层)。命令行解 tar.gz 用 tar -xzf,Java 程序内用两层流(压缩流套归档流)。
Q3:jshell 脚本跑完没有输出?
两个实测原因:脚本文件模式不回显表达式值,必须 System.out.println;导入子包要写全限定名,import org.apache.commons.compress.compressors.* 不包含 compressors.gzip 下的类。
Q4:终端里程序输出的中文变问号?
locale 问题。export LANG=C.UTF-8,或给 java/jshell 加 -Dfile.encoding=UTF-8。若终端是 GBK 编码的远程工具,反过来设 -Dfile.encoding=GBK 或改终端为 UTF-8。
五、参考链接
- Apache Commons Compress 官方主页:https://commons.apache.org/proper/commons-compress/
- 1.28.0 javadoc(Lister 及工厂 API):https://commons.apache.org/proper/commons-compress/javadocs/api-release/index.html
- 1.28.0 发行包下载:https://downloads.apache.org/commons/compress/binaries/commons-compress-1.28.0-bin.tar.gz
- Maven 中央仓库 1.28.0:https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.28.0/
- GNU tar 手册:https://www.gnu.org/software/tar/manual/
- gzip 手册:https://www.gnu.org/software/gzip/manual/
- XZ Utils:https://tukaani.org/xz/
- jshell 用户指南:https://docs.oracle.com/en/java/javase/17/jshell/