4

我想使用 JaxRS 从我的服务器创建并返回一个 zip 文件。我不认为我想在服务器上创建一个实际的文件,如果可能的话,我想动态创建 zip 并将其传递回客户端。如果我即时创建一个巨大的 zip 文件,如果 zip 文件中有太多文件,我会耗尽内存吗?

我也不确定最有效的方法。这就是我的想法,但是当涉及到 java 中的输入/输出时,我非常生疏。

public Response getFiles() {

// These are the files to include in the ZIP file       
String[] filenames = // ... bunch of filenames

byte[] buf = new byte[1024];

try {
   // Create the ZIP file
   ByteArrayOutputStream baos= new ByteArrayOutputStream();
   ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(baos));

   // Compress the files
   for (String filename : filenames) {
       FileInputStream in = new FileInputStream(filename);

       // Add ZIP entry to output stream.
       out.putNextEntry(new ZipEntry(filename));

       // Transfer bytes from the file to the ZIP file
       int len;
       while ((len = in.read(buf)) > 0) {
         out.write(buf, 0, len);
       }
       // Complete the entry
       out.closeEntry();
       in.close();
   }

   // Complete the ZIP file
   out.close();

   ResponseBuilder response = Response.ok(out);  // Not a 100% sure this will work
   response.type(MediaType.APPLICATION_OCTET_STREAM);
   response.header("Content-Disposition", "attachment; filename=\"files.zip\"");
   return response.build();

} catch (IOException e) {       
}

}

任何帮助将不胜感激。

4

2 回答 2

2

有两种选择:

1- 在临时目录中创建 ZIP,然后转储到客户端。

2-在创建它们时,使用响应中的 OutputStream 将 zip 直接发送到客户端。

但永远不要使用内存来创建巨大的 ZIP 文件。

于 2012-08-18T07:28:53.410 回答
0

在将 ZIP 文件提供给客户端之前,无需从内存中的第一个字节到最后一个字节创建 ZIP 文件。另外,也不需要提前在 temp 目录中创建这样的文件(特别是因为 IO 可能真的很慢)。

关键是开始流式传输“ZIP 响应”并在飞行中生成内容。

假设我们有一个aMethodReturningStream(),它返回一个Stream,我们希望将每个元素转换为一个存储在 ZIP 文件中的文件。而且我们不想将每个元素的字节一直存储在任何中间表示形式中,例如集合或数组。

那么这样的伪代码可能会有所帮助:

@GET
@Produces("application/zip")
public Response generateZipOnTheFly() {
    StreamingOutput output = strOut -> {
        try (ZipOutputStream zout = new ZipOutputStream(strOut)) {
            aMethodReturningStream().forEach(singleStreamElement -> {
                try {
                    ZipEntry zipEntry = new ZipEntry(createFileName(singleStreamElement));
                    FileTime fileTime = FileTime.from(singleStreamElement.getCreationTime());
                    zipEntry.setCreationTime(fileTime);
                    zipEntry.setLastModifiedTime(fileTime);
                    zout.putNextEntry(zipEntry);
                    zout.write(singleStreamElement.getBytes());
                    zout.flush();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    };
    return Response.ok(output)
                   .header("Content-Disposition", "attachment; filename=\"generated.zip\"")
                   .build();
}

这个概念依赖于将 a 传递StreamingOutputResponse构建器。StreamingOutput不是在发送响应之前生成的完整响应/实体/主体,而是用于动态生成字节流的配方(此处包含在ZipOutputStream中)。如果您对此不确定,则可以在下一个设置断点flush()并使用 eg 观察下载进度wget。这里要记住的关键是,这里的流不是预计算或预取项目的“包装器”。它必须是动态的,例如包装一个 DB 游标或类似的东西。此外,它可以被任何流数据取代。这就是为什么它不能是一个foreach遍历Element[] elems数组的循环(每个Element都有“内部”的所有字节),

for(Element elem: elems)

如果您想避免在流式传输 ZIP之前一次将所有项目读入堆中。

(请注意,这是一个伪代码,您可能还想添加更好的处理并完善其他内容。)

于 2020-10-27T13:11:45.760 回答