【问题标题】:Creating Zip file while client is downloading在客户端下载时创建 Zip 文件
【发布时间】:2014-10-10 06:08:37
【问题描述】:

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

有什么方法可以在压缩的同时发送文件吗?

感谢您的帮助。

【问题讨论】:

    标签: java multithreading io zip


    【解决方案1】:

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

    您将与之交互的部分是ZipOutputStream。它是一个FilterOutputStream(这意味着它旨在包装一个已经存在的outputstream - 在您的情况下,这将是响应的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)

    **编辑:**澄清......

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

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

    然后复制方法可能看起来像(这都是非常基本的 Java IO,您可以使用诸如 commons io 之类的库来避免重新发明轮子...)

    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 部分的错误)。

    【讨论】:

    • 这是一个了不起的答案。谢谢。
    • 关于将文件复制到流中,您能更具体一点吗?
    • 关于复制到输出流,以下内容可能也很有价值:stackoverflow.com/a/23252476/640539
    猜你喜欢
    • 1970-01-01
    • 2011-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-23
    • 2019-07-08
    • 1970-01-01
    • 2017-03-15
    相关资源
    最近更新 更多