Commons Compress调用压缩单个数据压缩器格式流的所有格式,而在单个(可能已压缩的)归档文件中收集多个条目的所有格式都是归档器格式。
压缩器支持的格式有gzip、bzip2、xz、lzma、Pack200、DEFLATE、Brotli、DEFLATE64、ZStandard和Z,归档器支持的格式有7z、ar、arj、cpio、dump、tar和zip。Pack200是一种特殊情况,因为它只能压缩JAR文件。
我们目前只提供对arj、dump、Brotli、DEFLATE64和z的读支持。arj只能读未压缩的档案,7z可以读7z支持的许多压缩和加密算法的档案,但不支持在写档案时加密。
流类都围绕着调用代码提供的流,它们直接处理这些流,而不需要任何额外的缓冲。另一方面,它们中的大多数将从缓冲中受益,因此强烈建议用户在使用Commons Compress API之前将流包装在 Buffered(In|Out)putStream
中。
Compress提供了工厂方法来基于压缩器或归档器格式的名称创建输入/输出流,以及尝试猜测输入流格式的工厂方法。
使用算法名创建一个压缩程序写入给定的输出:
CompressorOutputStream gzippedOut = new CompressorStreamFactory()
.createCompressorOutputStream(CompressorStreamFactory.GZIP, myOutputStream);
让工厂猜测给定存档流的输入格式:
ArchiveInputStream input = new ArchiveStreamFactory()
.createArchiveInputStream(originalInput);
让工厂猜测给定压缩机流的输入格式:
CompressorInputStream input = new CompressorStreamFactory()
.createCompressorInputStream(originalInput);
注意,无法检测lzma或Brotli格式,因此只能使用双参数版本的createCompressorInputStream。在压缩1.9之前,也没有自动检测到. z格式。
从Compress 1.14开始,CompressorStreamFactory
有一个可选的构造函数参数,可以用来设置在解压或压缩流时可能使用的内存上限。在1.14中,此设置只影响对Z、XZ和LZMA压缩流的解压。
因为Compress 1.19, SevenZFile
也有一个可选的构造函数来传递内存上限。支持LZMA压缩流。
对于Snappy和LZ4格式,压缩期间使用的内存量直接与窗口大小成比例。
从Compress 1.17开始,大多数的 CompressorInputStream
实现以及ZipArchiveInputStream
和 ZipFile.getInputStream
返回的所有流。实现InputStreamStatistics
接口。SevenZFile
通过 getStatisticsForCurrentEntry
方法提供当前条目的统计信息。该接口可用于在提取流时跟踪进度,或在压缩比变得可疑大时检测潜在的zip炸弹。
许多受支持的格式开发了不同的方言和扩展,有些格式允许Commons Compress支持的特性(还没有)。
ArchiveInputStream
类提供了一个方法 canReadEntryData
,如果Commons Compress可以检测到存档使用了当前实现不支持的特性,那么该方法将返回false。如果它返回false,您不应该尝试读取该条目,而应该跳过它。
所有归档格式都通过 ArchiveEntry
的实例(或者更确切地说,它的子类)提供关于单个归档条目的元数据。从归档中读取时,getName方法提供的信息是存储在归档中的原始名称。不能保证该名称表示一个相对文件名,甚至是目标操作系统上的一个有效文件名。当尝试从条目名称创建文件名时,应该再次检查结果。
除了7z之外,所有格式都提供了 ArchiveInputStream
的一个子类,可以用来创建归档。对于7z, SevenZFile
提供了一个类似的API,它不表示流,因为我们的实现要求对输入进行随机访问,不能用于一般的流。ZIP实现也可以从随机访问中获益很多,详细信息请参阅ZIP页面。
假设您想要将归档提取到一个目标目录(您将调用 getNextEntry
),验证可以读取条目,从条目的名称构造一个健全的文件名,创建一个 File
并将所有内容写入其中——这里是 IOUtils.copy
可能会派上用场。在 getNextEntry
返回 null
之前,您可以对每个条目执行此操作。
骨骼可能看起来像:
File targetDir = ...
try (ArchiveInputStream i = ... create the stream for your format, use buffering...) {
ArchiveEntry entry = null;
while ((entry = i.getNextEntry()) != null) {
if (!i.canReadEntryData(entry)) {
// log something?
continue;
}
String name = fileName(targetDir, entry);
File f = new File(name);
if (entry.isDirectory()) {
if (!f.isDirectory() && !f.mkdirs()) {
throw new IOException("failed to create directory " + f);
}
} else {
File parent = f.getParentFile();
if (!parent.isDirectory() && !parent.mkdirs()) {
throw new IOException("failed to create directory " + parent);
}
try (OutputStream o = Files.newOutputStream(f.toPath())) {
IOUtils.copy(i, o);
}
}
}
}
其中假设的 fileName
方法是由您编写的,并为将要写入到磁盘上的文件提供绝对名称。在这里,您应该执行检查,以确保生成的文件名实际上是操作系统上的有效文件名,或者在使用条目的名称作为输入时属于 targetDir
中的某个文件。
如果您想将存档格式与压缩格式结合在一起——比如在读取“tar”时。你把 ArchiveInputStream
包在 CompressorInputStream
周围,例如:
try (InputStream fi = Files.newInputStream(Paths.get("my.tar.gz"));
InputStream bi = new BufferedInputStream(fi);
InputStream gzi = new GzipCompressorInputStream(bi);
ArchiveInputStream o = new TarArchiveInputStream(gzi)) {
}