通过独立的块压缩文件,然后将它们连接成一个有效的存档

compressing file by independent chunks and then concat them into one valid archive

我想知道是否可以通过独立的块压缩任意文件(或文件夹,或任何其他文件结构),然后通过将它们连接在一起来获得有效的存档(例如 gzip)。一些要求:

看起来我需要先创建一个存档 header,然后再将压缩块附加到它 https://www.rfc-editor.org/rfc/rfc1952,但是我不确定它是否被任何人支持标准 java 实用程序或第 3 方库。有人知道从哪里开始吗?

一些背景: 我有一个 client-server 应用程序,它允许用户将文件上传到云存储。通过 REST api 通信,客户端将负责将文件分成块并一个一个地上传。可以在浏览器中进行压缩,但是我想知道我们是否可以将该负载移动到后端。

您可以为 tar + gzip 尝试这样的操作:

Maven 依赖项:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.18</version>
</dependency>

Java 压缩成块的代码:

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

[..]

private static final int MAX_CHUNK_SIZE = 16000000;

public void compressTarGzChunks(String inputDirPath, String outputDirPath) throws Exception {
    PipedInputStream in = new PipedInputStream();
    final PipedOutputStream out = new PipedOutputStream(in);
    new Thread(() -> {
        try {
            int chunkIndex = 0;
            int n = 0;
            byte[] buffer = new byte[8192];
            do {
                String chunkFileName = String.format("archive-part%d.tar.gz", chunkIndex);
                try (OutputStream fOut = Files.newOutputStream(Paths.get(outputDirPath, chunkFileName));
                     BufferedOutputStream bOut = new BufferedOutputStream(fOut);
                     GzipCompressorOutputStream gzOut = new GzipCompressorOutputStream(bOut)) {

                    int currentChunkSize = 0;
                    if (chunkIndex > 0) {
                        gzOut.write(buffer, 0, n);
                        currentChunkSize += n;
                    }
                    while ((n = in.read(buffer)) != -1 && currentChunkSize + n < MAX_CHUNK_SIZE) {
                        gzOut.write(buffer, 0, n);
                        currentChunkSize += n;
                    }
                    chunkIndex++;
                }
            } while (n != -1);
            in.close();
        } catch (IOException e) {
            // logging and exception handling should go here
        }
    }).start();

    try (TarArchiveOutputStream tOut = new TarArchiveOutputStream(out)) {
        compressTar(tOut, inputDirPath, "");
    }
}

private static void compressTar(TarArchiveOutputStream tOut, String path, String base)
        throws IOException {
    File file = new File(path);
    String entryName = base + file.getName();
    TarArchiveEntry tarEntry = new TarArchiveEntry(file, entryName);
    tarEntry.setSize(file.length());
    tOut.putArchiveEntry(tarEntry);

    if (file.isFile()) {
        try (FileInputStream in = new FileInputStream(file)) {
            IOUtils.copy(in, tOut);
            tOut.closeArchiveEntry();
        }
    } else {
        tOut.closeArchiveEntry();
        File[] children = file.listFiles();
        if (children != null) {
            for (File child : children) {
                compressTar(tOut, child.getAbsolutePath(), entryName + "/");
            }
        }
    }
}

Java 将块连接成单个存档的代码:

public void concatTarGzChunks(List<InputStream> sortedTarGzChunks, String outputFile) throws IOException {
    try {
        try (FileOutputStream fos = new FileOutputStream(outputFile)) {
            for (InputStream in : sortedTarGzChunks) {
                int len;
                byte[] buf = new byte[1024 * 1024];
                while ((len = in.read(buf)) != -1) {
                    fos.write(buf, 0, len);
                }
            }
        }
    } finally {
        sortedTarGzChunks.forEach(is -> {
            try {
                is.close();
            } catch (IOException e) {
                // logging and exception handling should go here
            }
        });
    }
}

是的。根据标准 (RFC 1952),gzip 文件的串联是一个有效的 gzip 文件。 gzip 当然可以处理这个问题。

您担心某些代码可能不支持它是正确的,因为连接 gzip 成员并不常见。如果你想超级安全,你可以将 gzip 文件组合成一个 gzip 成员,而不必重新压缩。但是,您确实需要通读所有压缩数据,有效地将其解压缩到内存中(这仍然比压缩快得多)。您可以在 gzjoin.c.

中找到相关示例