Mee*_*tan 6 java io stream tar archive
我有一个网络应用程序,我需要能够为用户提供多个文件的存档。我已经设置了一个 generic ArchiveExporter
,并做了一个ZipArchiveExporter
. 做工精美!我可以将数据流式传输到我的服务器,存档数据并将其流式传输给用户,而无需使用太多内存,也不需要文件系统(我在 Google App Engine 上)。
然后我想起了 zip64 和 4gb zip 文件的整个过程。我的档案可能会变得非常大(高分辨率图像),因此我希望有一个选项可以避免使用 zip 文件来处理较大的输入。
我检查了一下org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
,以为我找到了我需要的东西!遗憾的是,当我检查文档时,遇到了一些错误;我很快发现你必须在流式传输时传递每个条目的大小。这是一个问题,因为数据正在流式传输给我,而无法事先知道其大小。
我尝试计算并返回来自 的写入字节export()
,但预计之前的TarArchiveOutputStream
大小TarArchiveEntry
,因此这显然不起作用。
我可以用一个ByteArrayOutputStream
并在编写每个条目的内容之前完全阅读它,这样我就知道它的大小,但我的条目可能会变得非常大;这对于实例上运行的其他进程来说不太礼貌。
我可以使用某种形式的持久性,上传条目并查询数据大小。然而,这会浪费我的 google storage api 调用、带宽、存储和运行时间。
我知道这个问题问了几乎同样的事情,但他选择使用 zip 文件,并且没有更多相关信息。
创建包含未知大小条目的 tar 存档的理想解决方案是什么?
public abstract class ArchiveExporter<T extends OutputStream> extends Exporter { //base class
public abstract void export(OutputStream out); //from Exporter interface
public abstract void archiveItems(T t) throws IOException;
}
public class ZipArchiveExporter extends ArchiveExporter<ZipOutputStream> { //zip class, works as intended
@Override
public void export(OutputStream out) throws IOException {
try(ZipOutputStream zos = new ZipOutputStream(out, Charsets.UTF_8)) {
zos.setLevel(0);
archiveItems(zos);
}
}
@Override
protected void archiveItems(ZipOutputStream zos) throws IOException {
zos.putNextEntry(new ZipEntry(exporter.getFileName()));
exporter.export(zos);
//chained call to export from other exporter like json exporter for instance
zos.closeEntry();
}
}
public class TarArchiveExporter extends ArchiveExporter<TarArchiveOutputStream> {
@Override
public void export(OutputStream out) throws IOException {
try(TarArchiveOutputStream taos = new TarArchiveOutputStream(out, "UTF-8")) {
archiveItems(taos);
}
}
@Override
protected void archiveItems(TarArchiveOutputStream taos) throws IOException {
TarArchiveEntry entry = new TarArchiveEntry(exporter.getFileName());
//entry.setSize(?);
taos.putArchiveEntry(entry);
exporter.export(taos);
taos.closeArchiveEntry();
}
}
Run Code Online (Sandbox Code Playgroud)
编辑这就是我对 的想法ByteArrayOutputStream
。它有效,但我不能保证我总是有足够的内存来一次存储整个条目,因此我的流媒体工作。必须有一种更优雅的方式来传输 tarball!也许这是一个更适合 Code Review 的问题?
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();
}
}
Run Code Online (Sandbox Code Playgroud)
编辑这就是我将条目上传到介质(在本例中为谷歌云存储)以准确查询整个大小的意思。对于一个看似简单的问题来说,这似乎是一个重大的矫枉过正,但这不会遇到与上述解决方案相同的内存问题。只是以带宽和时间为代价。我希望有比我聪明的人出现并让我很快感到愚蠢: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();
}
Run Code Online (Sandbox Code Playgroud)
我一直在研究类似的问题,据我所知,这是tar 文件格式的限制。
Tar 文件被写入为流,元数据(文件名、权限等)被写入文件数据(即元数据 1、文件数据 1、元数据 2、文件数据 2 等)之间。提取数据的程序,它读取元数据 1,然后开始提取文件数据 1,但它必须有一种方法知道它何时完成。这可以通过多种方式完成;tar 通过在元数据中包含长度来实现此目的。
根据您的需求以及收件人的期望,我可以看到一些选项(并非全部适用于您的情况):
有趣的是,gzip 没有预定义的限制,并且多个 gzip 可以连接在一起,每个 gzip 都有自己的“原始文件名”。不幸的是,标准的gunzip 使用(?)第一个文件名将所有结果数据提取到一个文件中。