java 对于已经压缩的文件,是否使用ZipEntry.STORED?

qv7cva1a  于 2023-05-21  发布在  Java
关注(0)|答案(2)|浏览(242)

我正在使用ZipOutputStream来压缩一堆文件,这些文件混合了已经压缩的格式以及许多大型高度可压缩的格式,如纯文本。
大多数已经压缩的格式都是大文件,花费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();

有没有一种方法可以提供这些信息,而不必读取输入流两次?

hfyxw5xn

hfyxw5xn1#

简短回答:

考虑到我必须解决这个问题的时间,我无法确定一种方法来只读取文件一次并使用标准库计算CRC
我确实发现了一个优化,平均减少了大约50%的时间。
我预先计算要与ExecutorCompletionService(限制为Runtime.getRuntime().availableProcessors())并发存储的文件的CRC,并等待它们完成。其有效性根据需要计算CRC的文件数量而有所不同。文件越多,收益越大。
然后在.postVisitDirectories()中,我将ZipOutputStream Package 在运行在临时Thread上的PipedInputStream/PipedOutputStream对的PipedOutputStream周围,以将ZipOutputStream转换为InputStream,我可以将InputStream传递到HttpRequest中,以将ZipOutputStream的结果上传到远程服务器,同时串行写入所有预先计算的ZipEntry/Path对象。
现在,这已经足够好了,可以处理300+GB的即时需求,但是当我开始处理10TB的工作时,我将考虑解决它,并试图在不增加太多复杂性的情况下找到更多的优点。
如果我想出了一些实质上更好的时间明智的,我将更新这个答案与新的实现。

长回答:

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

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

ZipOutputStream.setLevel(NO_COMPRESSION/DEFAULT_COMPRESSION)黑客不是一个可行的方法。我对数百个数据、数千个文件夹和文件进行了广泛的测试,测量结果是决定性的。它在计算STORED文件的CRC与将它们压缩到NO_COMPRESSION之间没有任何好处。居然慢了一大截!
在我的测试中,文件位于网络安装的驱动器上,因此通过网络阅读已经压缩的文件两次以计算CRC,然后再次添加到ZipOutputStream,与只处理一次所有文件DEFLATED并在ZipOutputStream上更改.setLevel()一样快或更快。
网络访问没有本地文件系统缓存。这是一个更糟糕的情况,因为本地文件系统缓存,在本地磁盘上处理文件的速度要快得多。
因此,这种黑客攻击是一种天真的方法,是基于错误的假设。即使在NO_COMPRESSION级别,它也通过压缩算法处理数据,开销高于阅读文件两次。

ecr0jaav

ecr0jaav2#

考虑到我必须解决这个问题的时间,我无法确定一种方法来只读取文件一次并使用标准库计算CRC
我确实发现了一个优化,平均减少了大约50%的时间。
我预先计算要并发存储的文件的CRC...
与交替使用ZipOutputStream.setLevel(Deflater.NO_COMPRESSION)ZipOutputStream.setLevel(Deflater.DEFAULT_COMPRESSION)相比,我已经测量了相同的改进,而没有并发的CRC计算:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

...

    void addTo(ZipOutputStream zipOut, Path file) throws IOException {
        try (FileChannel fch = FileChannel.open(file)) {
            MappedByteBuffer buf = fch.map(MapMode.READ_ONLY, 0, fch.size());
            ZipEntry entry = new ZipEntry(relativize(file));
            entry.setLastModifiedTime(Files.getLastModifiedTime(file));
            if (entry.getName().endsWith(".zip")
                    || entry.getName().endsWith(".gz")) {
                entry.setMethod(ZipEntry.STORED);
                entry.setSize(buf.remaining());
                entry.setCrc(checkSum(buf));
            }
            zipOut.putNextEntry(entry);
            @SuppressWarnings("resource")
            WritableByteChannel zipCh = Channels.newChannel(zipOut);
            zipCh.write(buf);
            zipOut.closeEntry();
        }
    }

    static long checkSum(ByteBuffer buf) {
        CRC32 crc = new CRC32();
        int mark = buf.position();
        crc.update(buf);
        buf.position(mark);
        return crc.getValue();
    }

(The relativize(Path) : String方法不在示例中。)
CRC32类提供了非常有效的update(ByteBuffer)方法,用于内存Map(直接)文件缓冲区。

相关问题