我有一个web应用程序,我需要能够为用户提供多个文件的存档。我已经建立了一个通用的
ArchiveExporter
,并使
ZipArchiveExporter
. 很好用!我可以将我的数据流到我的服务器上,并将数据存档并流到用户那里,而无需使用大量内存,也无需文件系统(我在Google App Engine上)。
我退房了
org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
以为我找到了我需要的!可悲的是,当我查看文档时,遇到了一些错误;我很快就发现了你
在传输时传递每个条目的大小。这是一个问题,因为数据是流到我没有办法事先知道大小。
我试着计算并返回
export()
,但是
TarArchiveOutputStream
预计尺寸为
TarArchiveEntry
之前
我可以用一个
ByteArrayOutputStream
并且在编写内容之前完全阅读每个条目,这样我就知道它的大小,但是我的条目可能会非常大;这对运行在实例上的其他进程来说不是很礼貌。
我知道
this
创建包含未知大小条目的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();
}
}
编辑
我就是这么想的
ByteArrayOutputStream公司
. 它可以工作,但我不能保证我总是有足够的内存来存储整个条目,因此我的流媒体工作。必须有一个更优雅的方式流一个柏油球!也许这是一个更适合代码评审的问题?
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();
}
}
编辑
这就是我所说的将条目上传到一个媒体(本例中是Google云存储)以精确查询整个大小。看起来像是一个简单问题的主要过度杀戮,但这并不像上面的解决方案那样遭受相同的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();
}