Java创建具有未知大小项的tar存档

Java创建具有未知大小项的tar存档,java,io,stream,tar,archive,Java,Io,Stream,Tar,Archive,我有一个web应用程序,我需要能够为用户提供多个文件的存档。我已经建立了一个通用的ArchiveExporter,并制作了一个ZipArchiveExporter。工作得很漂亮!我可以将数据流式传输到我的服务器,归档数据并将其流式传输给用户,而无需使用太多内存,也无需文件系统(我使用的是Google App Engine) 然后我想起了zip64的4gb压缩文件。我的归档文件可能会变得非常大(高分辨率图像),因此我希望有一个选项来避免在较大的输入中使用zip文件 我退房了,以为我找到了我需要的东

我有一个web应用程序,我需要能够为用户提供多个文件的存档。我已经建立了一个通用的
ArchiveExporter
,并制作了一个
ZipArchiveExporter
。工作得很漂亮!我可以将数据流式传输到我的服务器,归档数据并将其流式传输给用户,而无需使用太多内存,也无需文件系统(我使用的是Google App Engine)

然后我想起了zip64的4gb压缩文件。我的归档文件可能会变得非常大(高分辨率图像),因此我希望有一个选项来避免在较大的输入中使用zip文件

我退房了,以为我找到了我需要的东西!可悲的是,当我检查文档时,遇到了一些错误;我很快发现,您必须在流媒体时传递每个条目的大小。这是一个问题,因为数据流传送给我之前无法知道大小

我尝试从
export()
计算并返回写入的字节数,但
TarArchiveOutputStream
在写入之前需要
TarArchiveEntry
中的大小,因此显然不起作用

我可以使用一个
ByteArrayOutputStream
,在写内容之前完全阅读每个条目,这样我就知道了它的大小,但我的条目可能会很大;这对于实例上运行的其他进程来说不是很礼貌

我可以使用某种形式的持久性,上传条目,并查询数据大小。然而,这将浪费我的google存储api调用、带宽、存储和运行时

我知道他问了差不多同样的问题,但他决定使用zip文件,没有更多相关信息

用未知大小的条目创建tar存档的理想解决方案是什么?

编辑我的意思是将条目上传到媒体(本例中为谷歌云存储)以准确查询整个大小。对于一个看似简单的问题来说,这似乎是一个重大的过度杀伤力,但这并不像上面的解决方案那样存在同样的ram问题。只是以带宽和时间为代价。我希望有比我更聪明的人过来,让我很快觉得自己很愚蠢:D

protected void googleCloudStorageTempFileApproach(TarArchiveOutputStream taos) throws IOException {
    TarArchiveEntry entry = new TarArchiveEntry(exporter.getFileName());
    String name = NameHelper.getRandomName(); //get random name for temp storage
    BlobInfo blobInfo = BlobInfo.newBuilder(StorageHelper.OUTPUT_BUCKET, name).build(); //prepare upload of temp file
    WritableByteChannel wbc = ApiContainer.storage.writer(blobInfo); //get WriteChannel for temp file
    try(OutputStream out = Channels.newOutputStream(wbc)) {
        exporter.export(out); //stream items to remote temp file
    } finally {
        wbc.close();
    }

    Blob blob = ApiContainer.storage.get(blobInfo.getBlobId());
    long size = blob.getSize(); //accurately query the size after upload
    entry.setSize(size);
    taos.putArchiveEntry(entry);

    ReadableByteChannel rbc = blob.reader(); //get ReadChannel for temp file
    try(InputStream in = Channels.newInputStream(rbc)) {
        IOUtils.copy(in, taos); //stream back to local tar stream from remote temp file 
    } finally {
        rbc.close();
    }
    blob.delete(); //delete remote temp file

    taos.closeArchiveEntry();
}

我一直在研究一个类似的问题,据我所知,这是一个限制

Tar文件作为流写入,元数据(文件名、权限等)在文件数据(即元数据1、文件数据1、元数据2、文件数据2等)之间写入。提取数据的程序读取元数据1,然后开始提取文件数据1,但它必须有一种知道何时完成的方法。这可以通过多种方式实现;tar通过在元数据中包含长度来实现这一点

根据您的需要和收件人的期望,我可以看到一些选项(并非所有选项都适用于您的情况):

  • 正如您提到的,加载整个文件,计算长度,然后发送
  • 将文件划分为预定义长度(适合内存)的块,然后将它们标记为file1-part1、file1-part2等。;最后一个街区很短
  • 将文件分成预定义长度的块(不需要放入内存),然后用适当的东西将最后一个块填充到该大小
  • 计算出文件的最大可能大小,并填充到该大小
  • 使用不同的存档格式
  • 制作您自己的存档格式,它没有此限制
  • 有趣的是,gzip没有预定义的限制,多个gzip可以连接在一起,每个gzip都有自己的“原始文件名”。不幸的是,标准gunzip使用(?)第一个文件名将所有结果数据提取到一个文件中

    protected void byteArrayOutputStreamApproach(TarArchiveOutputStream taos) throws IOException {
        TarArchiveEntry entry = new TarArchiveEntry(exporter.getFileName());
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            exporter.export(baos);
            byte[] data = baos.toByteArray();
            //holding ENTIRE entry in memory. What if it's huge? What if it has more than Integer.MAX_VALUE bytes? :[
            int len = data.length;
            entry.setSize(len);
            taos.putArchiveEntry(entry);
            taos.write(data);
            taos.closeArchiveEntry();
        }
    }
    
    protected void googleCloudStorageTempFileApproach(TarArchiveOutputStream taos) throws IOException {
        TarArchiveEntry entry = new TarArchiveEntry(exporter.getFileName());
        String name = NameHelper.getRandomName(); //get random name for temp storage
        BlobInfo blobInfo = BlobInfo.newBuilder(StorageHelper.OUTPUT_BUCKET, name).build(); //prepare upload of temp file
        WritableByteChannel wbc = ApiContainer.storage.writer(blobInfo); //get WriteChannel for temp file
        try(OutputStream out = Channels.newOutputStream(wbc)) {
            exporter.export(out); //stream items to remote temp file
        } finally {
            wbc.close();
        }
    
        Blob blob = ApiContainer.storage.get(blobInfo.getBlobId());
        long size = blob.getSize(); //accurately query the size after upload
        entry.setSize(size);
        taos.putArchiveEntry(entry);
    
        ReadableByteChannel rbc = blob.reader(); //get ReadChannel for temp file
        try(InputStream in = Channels.newInputStream(rbc)) {
            IOUtils.copy(in, taos); //stream back to local tar stream from remote temp file 
        } finally {
            rbc.close();
        }
        blob.delete(); //delete remote temp file
    
        taos.closeArchiveEntry();
    }