如何在Java中定期刷新ZipOutputStream?

3

我正在尝试将文件列表以zip格式存档,然后在用户使用时进行下载...

当下载1GB大小的zip文件时,我遇到了内存不足的问题。

请帮助我如何解决这个问题而不增加jvm堆大小。我想定期刷新流..

我已经尝试了定期刷新,但对我没有用。

请查看下面附上的代码:

try{
ServletOutputStream out = response.getOutputStream();
        ZipOutputStream zip = new ZipOutputStream(out);

        response.setContentType("application/octet-stream");
        response.addHeader("Content-Disposition",
                "attachment; filename=\"ResultFiles.zip\"");
                  //adding multiple files to zip
        ZipUtility.addFileToZip("c:\\a", "print1.txt", zip);
ZipUtility.addFileToZip("c:\\a", "print2.txt", zip);
ZipUtility.addFileToZip("c:\\a", "print3.txt", zip);
ZipUtility.addFileToZip("c:\\a", "print4.txt", zip);

zip.flush();        
zip.close();
out.close();
} catch (ZipException ex) {
            System.out.println("zip exception");             
        } catch (Exception ex) {
            System.out.println("exception");
            ex.printStackTrace();   
}

public class ZipUtility {

    static public void addFileToZip(String path, String srcFile,
            ZipOutputStream zip) throws Exception {

        File file = new File(path + "\\" + srcFile);
        boolean exists = file.exists();
        if (exists) {

            long fileSize = file.length();
            int buffersize = (int) fileSize;
            byte[] buf = new byte[buffersize];

            int len;
            FileInputStream fin = new FileInputStream(path + "\\" + srcFile);
            zip.putNextEntry(new ZipEntry(srcFile));
            int bytesread = 0, bytesBuffered = 0;
            while ((bytesread = fin.read(buf)) > -1) {
                zip.write(buf, 0, bytesread);
                bytesBuffered += bytesread;
                if (bytesBuffered > 1024 * 1024) { //flush after 1mb
                    bytesBuffered = 0;
                    zip.flush();

                }
            }
            zip.closeEntry();
            zip.flush();
            fin.close();
        }

    }
}

}

1
ZipOutputStream中有压缩吗?如果有,那么就不能刷新它。 - Fender
4个回答

3
您想使用分块编码(Chunked-Encoding)发送大文件,否则servlet容器将在发送数据之前尝试计算其大小以设置Content-Length头。由于您正在压缩文件,因此不知道要发送的数据的大小。分块编码允许您以更小的块发送响应的片段。请勿设置流的内容长度。您可以尝试使用curl等工具查看从服务器获取的HTTP响应头。如果它不是分块的,则需要找出原因。您需要研究如何强制servlet容器发送分块编码,可能需要将其添加到响应头中才能实现分块发送。
response.setHeader("Transfer-Encoding", "chunked");

另一个选择是使用File.createTemp()将文件压缩到临时文件中,然后发送该文件的内容。如果先将文件压缩到临时文件中,则可以知道文件的大小,并为servlet设置内容长度。

我在代码中尝试设置响应: response.setHeader("Transfer-Encoding", "chunked"); 但是它没有起作用。 - Hima Bindu
你需要弄清楚你的服务器是否正在尝试以分块编码发送此文件。如果它已经以分块方式发送,并且你仍然有问题,那么我敢打赌这与使用zip有关。我建议尝试使用CURL或Web浏览器查看用户尝试下载文件时响应中的标头。查看传输编码是否为分块(在响应中没有设置参数)。如果它在标头中存在,则不是问题,很可能是你使用Zip的方式有误。 - chubbsondubs

1

另外一件事...你正在try catch块内执行close操作。如果出现异常,这会使流保持打开状态,并且不会给流刷新到磁盘的机会。

始终确保你的close操作在finally块中(至少在你可以使用Java 7及其try-with-resources块之前)。

//build the byte buffer for transferring the data from the file
//to the zip.
final int BUFFER = 2048;
byte [] data = new byte[BUFFER];

File zipFile= new File("C\:\\myZip.zip");

BufferedInputStream in = null;
ZipOutputStream zipOut = null;

try {
  //create the out stream to send the file to and zip it.
  //we want it buffered as that is more efficient.
  FileOutputStream destination = new FileOutputStream(zipFile);
  zipOut = new ZipOutputStream(new BufferedOutputStream(destination));
  zipOut.setMethod(ZipOutputStream.DEFLATED);

  //create the input stream (buffered) to read in the file so we
  //can write it to the zip.
  in = new BufferedInputStream(new FileInputStream(fileToZip), BUFFER);

  //now "add" the file to the zip (in object speak only).
  ZipEntry zipEntry = new ZipEntry(fileName);
  zipOut.putNextEntry(zipEntry);

  //now actually read from the file and write the file to the zip.
  int count;
  while((count = in.read(data, 0, BUFFER)) != -1) {
zipOut.write(data, 0, count);
  }
}
catch (FileNotFoundException e) {
  throw e;
}
catch (IOException e) {
  throw e;
}
finally {
 //whether we succeed or not, close the streams.
 if(in != null) {
try {
  in.close();
}
catch (IOException e) {
  //note and do nothing.
      e.printStackTrace();
}
 }
 if(zipOut != null) {
try {
      zipOut.close();
}
catch (IOException e) {
      //note and do nothing.
      e.printStackTrace();
    }
 }
}

现在,如果您需要循环,只需循环需要添加更多文件的部分。也许可以传入一个文件数组并对其进行循环。这段代码对我压缩文件非常有效。

因为我能够下载ZIP归档文件,如果ZIP大小较小,即约为160MB。 - Hima Bindu
我已经压缩了12 MB的东西。我可以试试一千兆字节。我会告诉你的。 - Chris Aldrich

1

我猜你可能在错误的方向上挖掘。尝试用文件流替换servlet输出流,看看问题是否仍然存在。我怀疑你的Web容器在发送HTTP头之前尝试收集整个servlet输出以计算内容长度。


另外,我猜你需要刷新Servlet流,而不是Zip流。 - kan
2
刷新 zip 流将刷新 servlet 流。 - jtahlborn

1

不要根据文件大小调整buf的大小,使用固定大小的缓冲区。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接