4

我正在使用 aZipOutputStream压缩一堆文件,这些文件混合了已经压缩的格式以及许多大型高度可压缩格式,如纯文本。

大多数已经压缩的格式都是大文件,花费 cpu 和内存重新压缩它们是没有意义的,因为它们永远不会变小,有时在极少数情况下会稍微变大。

我在检测到预压缩文件时尝试使用.setMethod(ZipEntry.STORED),但它抱怨我需要size, compressedSize and crc为这些文件提供。

我可以使用以下方法使其工作,但这需要我读取文件两次。一次计算CRC32然后再次实际将文件复制到ZipOutputStream.

// code that determines the value of method omitted for brevity
if (STORED == method)
{
    fze.setMethod(STORED);
    fze.setCompressedSize(fe.attributes.size());
    final HashingInputStream his = new HashingInputStream(Hashing.crc32(), fis);
    ByteStreams.copy(his,ByteStreams.nullOutputStream());
    fze.setCrc(his.hash().padToLong());
}
else
{
    fze.setMethod(DEFLATED);
}
zos.putNextEntry(fze);
ByteStreams.copy(new FileInputStream(fe.path.toFile()), zos);
zos.closeEntry();

有没有办法提供这些信息而不必两次读取输入流?

4

1 回答 1

1

简短的回答:

CRC考虑到我必须解决这个问题的时间,我无法确定一种只读取文件一次并使用标准库计算的方法。

我确实找到了一个优化,平均减少了大约50%时间。

我预先计算了CRC要同时存储的文件的ExecutorCompletionService数量,Runtime.getRuntime().availableProcessors()并等待它们完成。CRC其有效性因需要计算的文件数量而异。文件越多,收益越大。

然后在.postVisitDirectories()我将一个在一个临时运行的对中ZipOutputStream环绕一个,以将其转换为一个我可以传递到的以将结果上传到远程服务器,同时串行写入所有预先计算的对象。PipedOutputStreamPipedInputStream/PipedOutputStreamThreadZipOutputStreamInputStreamHttpRequestZipOutputStreamZipEntry/Path

现在这已经足够好,可以处理300+GB即时需求,但是当我开始10TB工作时,我会考虑解决它并尝试在不增加太多复杂性的情况下找到更多优势。

如果我想出一些更好的时间明智的东西,我会用新的实现来更新这个答案。

长答案:

我最终编写了一个干净的房间ZipOutputStream,它支持多部分 zip 文件、智能压缩级别 vsSTORE并且能够在CRC我读取时计算,然后在流的末尾写出元数据。


为什么 ZipOutputStream.setLevel() 交换不起作用:

ZipOutputStream.setLevel(NO_COMPRESSION/DEFAULT_COMPRESSION) hack 不是一种可行的方法。我对数百个数据、数千个文件夹和文件进行了广泛的测试,结果得出了结论。计算文件与压缩文件相比,CRC它 没有任何好处。它实际上 要慢很多!STOREDNO_COMPRESSION

在我的测试中,文件位于网络安装的驱动器上,因此通过网络读取已经压缩文件两次的文件以计算CRC然后再次添加到的速度ZipOutputStream与仅处理所有文件一次DEFLATED 并更改.setLevel().ZipOutputStream

网络访问没有本地文件系统缓存。这是一个更糟糕的情况,由于本地文件系统缓存,处理本地磁盘上的文件会快得多。

因此,这种 hack 是一种幼稚的方法,并且基于错误的假设。它甚至在级别上通过压缩算法处理数据,NO_COMPRESSION并且开销高于两次读取文件。

于 2016-02-03T23:06:41.057 回答