自学内容网 自学内容网

文件下载ZIP报错:Out of Memory的问题排查

        废话少说,上干货!!

        项目中下载小文件或者下载成ZIP文件都正常,但下载超过2G的ZIP文件,报错:Out of Memory:Java Heap Space。

        内存溢出,可是大毛病,排查一下代码:

 private void setByteArrayOutputStream(String fileName, InputStream inputStream, ZipArchiveOutputStream zous) {
       
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        baos.flush();
        byte[] bytes = baos.toByteArray();
        //设置文件名
        ArchiveEntry entry = new ZipArchiveEntry(fileName);
        zous.putArchiveEntry(entry);
        zous.write(bytes);
        zous.closeArchiveEntry();
        baos.close();
}

        细心的童鞋应该已经发现错误了!

        一、原因分析

        原因分析主要与 ByteArrayOutputStream 的使用有关。ByteArrayOutputStream 会在内存中动态扩展其缓冲区以容纳写入的数据。当写入大量数据时,尤其是在处理大文件时,可能会导致内存频繁分配和复制,从而消耗大量内存。大文件处理:如果输入流(InputStream)读取的数据量很大(如超过几百MB或GB),ByteArrayOutputStream 可能会消耗超过可用内存的资源,导致 OutOfMemoryError。

        在上述代码中,逻辑如下:

        (1)读取数据:通过 inputStream.read(buffer) 持续将数据读取到缓冲区 buffer 中。

        (2)写入 ByteArrayOutputStream:每次读取的数据都被写入到 ByteArrayOutputStream 中。如果文件非常大,ByteArrayOutputStream 将不断扩展其内部数组。

       (3) 转换为字节数组:调用 baos.toByteArray() 时,会创建一个新的字节数组并将所有数据复制到这个新数组中,这又需要额外的内存。

        二、解决方案

        采用流式处理:直接从 InputStream 读取并写入到 ZipArchiveOutputStream,而不使用 ByteArrayOutputStream。这样可以避免将整个文件加载到内存中。

        代码如下:

​
 private void setByteArrayOutputStream(String fileName, InputStream inputStream, ZipArchiveOutputStream zous) {
        //创建Zip入口
        try {
            ZipArchiveEntry entry = new ZipArchiveEntry(fileName);
            zous.putArchiveEntry(entry);

            //使用流缓冲区,一次1024字节,分块读取并写入ZIP输出流
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                zous.write(buffer, 0, len);
            }
            //完成当前ZIP入口
            zous.closeArchiveEntry();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            //确保输入流被关闭
            try {
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

​

        运行之后,我们发现:不论多大的文件下载,不再报错内存溢出了。但对于超过了4G的文件,在形成ZIP包时,出现了其他错误,如下:

org.apache.commons.compress.archivers.zip.Zip64RequiredException: XXXXXXXXXXXXXXXXXXXXXXXXXXXX's size exceeds the limit of 4GByte.
at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.checkIfNeedsZip64(ZipArchiveOutputStream.java:651)
at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.handleSizesAndCrc(ZipArchiveOutputStream.java:638)
at org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream.closeArchiveEntry(ZipArchiveOutputStream.java:513)

        原因分析:

        这个异常通常是在尝试创建一个 ZIP 文件时遇到的,特别是当文件的大小超过 4GB 时。ZIP 格式的标准限制了单个文件的大小为 4GB,因此在处理大文件时需要使用 ZIP64 格式。我们在代码里添加如下:

 zous.setUseZip64(ZipArchiveOutputStream.Zip64Mode.Always); // 启用 ZIP64 支持

或者(不同版本,函数不一样)

  zous.setUseZip64(Zip64Mode.Always);

        按照以上改完之后,我们发现文件下载ZIP完全没有内存溢出错误了。


原文地址:https://blog.csdn.net/weixin_38203586/article/details/145117583

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!