在客户端下载时创建Zip文件

时间:2014-08-16 22:16:29

标签: java multithreading io zip

我尝试开发类似dropbox(非常基本的)。要下载一个文件,这很容易。只需使用servletoutputstream。我想要的是:当客户端问我多个文件时,我在服务器端压缩文件然后发送给用户。但是如果文件很大,则将它们压缩并发送给用户需要花费太多时间。

有没有办法在压缩时发送文件?

感谢您的帮助。

1 个答案:

答案 0 :(得分:4)

ZIP文件的部分Java API实际上是为了提供“即时”压缩而设计的。这一切都很适合java.io API和servlet API,这意味着它甚至......很简单(不需要多线程 - 即使出于性能原因,因为通常你的CPU在ZIPping上可能比你的网络更快将发送内容)。

您要与之互动的部分是ZipOutputStream。它是FilterOutputStream(这意味着它被设计为包含已存在的outputstream - 在您的情况下,它将是respone的OutputStream),并将压缩您发送它的每个字节,使用ZIP压缩。

所以,假设你有一个获取请求

protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    // Your code to handle the request
    List<YourFileObject> responseFiles = ... // Whatever you need to do 

    // We declare that the response will contain raw bytes
    response.setContentType("application/octet-stream");

    // We open a ZIP output stream
    try (ZipOutputStream zipStream = new ZipOutputStream(response.getOutputStream()) {// This is Java 7, but not that different from java 6

        // We need to loop over each files you want to send
        for(YourFileObject fileToSend : responseFiles) {
            // We give a name to the file
            zipStream.putNextEntry(new ZipEntry(fileToSend.getName()));
            // and we copy its content
            copy(fileToSend, zipStream);
        }
    }
}

当然,您应该进行适当的异常处理。虽然有几个快速说明:

  1. ZIP文件格式要求每个文件都有一个名称,因此每次启动新文件时都必须创建一个新的ZipEntry(如果不这样做,你可能会获得IllegalStateException
  2. 正确使用API​​的方法是,在完成写入后(文件末尾)关闭每个条目。但是:Java实现为您做到了这一点:每次调用putNextEntry时,它都会自动关闭前一个(如果需要)
  3. 同样,你不要忘记关闭ZIP流,因为这将正确关闭最后一个条目并刷新创建正确ZIP文件所需的一切。如果不这样做将导致文件损坏。这里,try with resources语句执行此操作:一旦写入所有内容,它就会关闭ZipOutputStream
  4. 这里的copy方法就是您用来将原始文件中的所有字节传输到输出流的方法,没有任何特定于ZIP的信息。只需致电outputStream.write(byte[] bytes)
  5. **编辑:**澄清......

    例如,给定YourFileType具有以下方法:

    public interface YourFileType {
    
        public byte[] getContent();
    
        public InputStream getContentAsStream();
    
    }
    

    然后复制方法看起来像(这是所有非常基本的Java IO,您可以使用像commons这样的库来重新发明轮子......)

    public void copy(YourFileType file, OutputStream os) throws IOException {
        os.write(file.getContent());
    }
    

    或者,对于完整的流媒体实施:

    public void copy(YourFileType file, OutputStream os) throws IOException {
        try (InputStream fileContent = file.getContentAsStream()) {
            byte[] buffer = new byte[4096]; // 4096 is kind of a magic number
            int readBytesCount = 0;
            while((readBytesCount = fileContent.read(buffer)) >= 0) {
                os.write(buffer, 0, readBytesCount);
            }
        }
    }
    

    使用这种实现方式,您的客户端几乎在您开始写入ZIPOutputStream时就会开始收到响应(唯一的延迟是内部缓冲区的延迟),这意味着它不会超时(除非您花了太长时间来发送内容 - 但这不是ZIPping部分的错误。)