使用 ServletOutputStream 在 Java servlet 中写入非常大的文件,而不会出现内存问题

2022-09-01 03:14:52

我正在使用IBM Websphere Application Server v6和Java 1.4,并尝试将大型CSV文件写入用户下载。目前,文件的范围从50-750MB不等。ServletOutputStream

较小的文件不会造成太大的问题,但是对于较大的文件,它似乎被写入堆中,然后导致OutOfMemory错误并导致整个服务器关闭。

这些文件只能通过HTTPS提供给经过身份验证的用户,这就是为什么我通过Servlet为他们提供服务,而不仅仅是将它们粘贴在Apache中。

我使用的代码是(围绕这个删除了一些绒毛):

    resp.setHeader("Content-length", "" + fileLength);
    resp.setContentType("application/vnd.ms-excel");
    resp.setHeader("Content-Disposition","attachment; filename=\"export.csv\"");

    FileInputStream inputStream = null;

    try
    {
        inputStream = new FileInputStream(path);
        byte[] buffer = new byte[1024];
        int bytesRead = 0;

        do
        {
            bytesRead = inputStream.read(buffer, offset, buffer.length);
            resp.getOutputStream().write(buffer, 0, bytesRead);
        }
        while (bytesRead == buffer.length);

        resp.getOutputStream().flush();
    }
    finally
    {
        if(inputStream != null)
            inputStream.close();
    }

这似乎并没有导致问题,就好像我写入另一个文件或只是完全删除写入一样,内存使用似乎不是问题。FileInputStream

我在想的是,存储在内存中,直到数据可以发送到客户端。因此,整个文件可能会被读取并存储在导致我的内存问题和崩溃中!resp.getOutputStream().writeresp.getOutputStream()

我尝试过缓冲这些流,也尝试使用 通道 来自 ,这些似乎都没有对我的内存问题产生任何影响。我还刷新了循环的每次迭代一次,并在循环之后刷新了一次,这无济于事。java.nioOutputStream


答案 1

默认情况下,普通的 servletcontainer 本身每大约 2KB 刷新一次流。在按顺序流式传输来自同一源的数据时,您真的不需要显式调用 at 间隔。例如,在Tomcat(和Websphere!)中,这可以配置为HTTP连接器的属性。flush()OutputStreamHttpServletResponsebufferSize

如果内容长度事先未知(根据Servlet API规范!)并且客户端支持HTTP 1.1,那么普通的servletcontainer也只是将数据以的形式流式传输。

问题症状至少表明 servlet 容器在刷新之前正在缓冲内存中的整个流。这可能意味着未设置内容长度标头和/或 servletcontainer 不支持分块编码和/或客户端不支持分块编码(即它使用 HTTP 1.0)。

要修复一个或另一个,只需事先设置内容长度:

response.setContentLengthLong(new File(path).length());

或者,当您尚未使用 Servlet 3.1 时:

response.setHeader("Content-Length", String.valueOf(new File(path).length()));

答案 2

在输出流上工作。flush

真的我想评论一下,你应该使用三参数形式的写入,因为缓冲区不一定是完全读取的(特别是在文件(!)的末尾)。此外,尝试/最终将是有序的,除非您希望服务器意外死亡。


推荐