【问题标题】:Streaming a POI workbook to the servlet output stream将 POI 工作簿流式传输到 servlet 输出流
【发布时间】:2010-04-20 08:49:02
【问题描述】:

我在我的网络服务器上构建了一个非常大的 POI 工作簿。将整个工作簿保存在内存中,不会针对多个并发请求进行扩展。有没有办法可以逐步将工作簿写入 servlet 输出流。这应该会减少响应时间,并提高进程内存效率。

【问题讨论】:

    标签: java servlets apache-poi


    【解决方案1】:

    如果您要生成 Excel 2007 (xslx),那么您可以调整 BigGridDemo.java 的方法,如下所述:http://web.archive.org/web/20110821054135/http://www.realdevelopers.com/blog/code/excel

    解决方案是让 POI 仅生成一个容器 xslx 作为模板,并将实际电子表格数据作为 XML 流式传输到 zip 输出流中。然后由您来简化 XML 生成。

    【讨论】:

    • 这就像一个魅力,是迄今为止最好的答案,因为这种方法既不需要过多的内存,也不需要将主要内容刷新到磁盘,也不会导致客户端上的 TTFB 超时。无需提前了解完整的文档内容。
    【解决方案2】:

    自从写完其余的答案后,情况有了很大改善——流现在是 Apache Poi 的一部分。

    查看SXSSFWorkbook 类和documentation here。它在工作表上使用流式窗口,将窗口外的旧行刷新到临时文件中。

    这是基于hlg's answer 中使用的BigGridDemo 方法,但现在是官方分发的一部分。

    这是文档中的示例:

    public static void main(String[] args) throws Throwable {
        // keep 100 rows in memory, exceeding rows will be flushed to disk
        SXSSFWorkbook wb = new SXSSFWorkbook(100); 
        Sheet sh = wb.createSheet();
        for(int rownum = 0; rownum < 1000; rownum++){
            Row row = sh.createRow(rownum);
            for(int cellnum = 0; cellnum < 10; cellnum++){
                Cell cell = row.createCell(cellnum);
                String address = new CellReference(cell).formatAsString();
                cell.setCellValue(address);
            }
    
        }
    
        // Rows with rownum < 900 are flushed and not accessible
        for(int rownum = 0; rownum < 900; rownum++){
          Assert.assertNull(sh.getRow(rownum));
        }
    
        // ther last 100 rows are still in memory
        for(int rownum = 900; rownum < 1000; rownum++){
            Assert.assertNotNull(sh.getRow(rownum));
        }
    
        FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
        wb.write(out);
        out.close();
    
        // dispose of temporary files backing this workbook on disk
        wb.dispose();
    }
    

    【讨论】:

    • 此示例似乎仍将整个工作簿保留在内存中,然后再将其流出。有趣的是,是否可以将 wb.write(out) 调用放入 for 循环中以解决最初的问题。
    • 问题不仅在于内存,还在于有时准备整个文档所需的时间。在数据可用时开始流式传输数据似乎是合理的,而不是先等待所有数据。
    • @SergeyShcherbakov 感谢您的评论,但恐怕这不正确。 SXSSFWorkbook 在后台将临时数据刷新到磁盘,因此此示例不会将整个工作簿保留在内存中。
    • 临时格式是必要的,因为 xlsx 格式不能写为流,除非您首先了解整个文档(它具有摘要信息和前向引用) - 这种临时格式由SXSSFWorkbook。有关详细信息,请参阅类文档。
    • 我已经使用此处获胜答案中的方法成功地将一个非常大的 Excel 文档流式传输到 Web 客户端。 web.archive.org/web/20110821054135/http://… 没有过多的内存,没有刷新到磁盘,客户端没有 TTFB 超时。
    【解决方案3】:

    不幸的是,如果没有顺序数据,这是不可能的。我建议寻找另一种格式,例如CSV 或 XML。两者都可以顺序写出。如果它来自数据库,它甚至可以更有效地完成,因为一个体面的数据库具有内置设施,可以有效地导出到这些格式。您只需要将字节从一侧流式传输到另一侧。

    【讨论】:

      【解决方案4】:

      您是否尝试过直接使用 HttpServletResponse.getOutputStream() 的 write 方法?

      请看下面的例子:

       HSSFWorkbook wb = new HSSFWorkbook();
       HSSFSheet sheet = wb.createSheet("new sheet");
       ...
       OutputStream out = response.getOutputStream();
       wb.write(out);
       out.close();
      

      【讨论】:

      • -1 这不能回答问题。 OP 明确指出他试图解决的问题是如何避免创建所有内容然后一次将其全部写出来。
      • -1 你不能流式传输 xlxs。就像提到的OP一样
      • 这仍将保留在内存中,直到所有行都写入 wb
      【解决方案5】:

      如果你使用JExcel 它具有从 Servlet 读取流代码的示例代码。 http://jexcelapi.sourceforge.net/resources/faq/

      这个 API 的唯一缺点是它只支持 Excel 2003(包括 Excel 2003)。

      使用 POI - 您能否不创建文件并将文件的字节提供给 servlet 输出流?

      【讨论】:

      • 我正在将 POI 工作簿写入 servlet 输出流。幕后发生的事情是将字节写入输出流。我的问题是,我不知道如何逐步编写它。我必须等到整个工作簿创建完成,然后才将其写入 I/O。写入 I/O 大约需要一分钟。不知道这是否合理。
      • 您的电子表格有多大?你创建电子表格,还是给你的?如果给你,你会修改吗?写 I/O 需要这么多时间?
      猜你喜欢
      • 2012-09-06
      • 1970-01-01
      • 1970-01-01
      • 2015-11-06
      • 2011-04-21
      • 2016-03-01
      • 2019-07-10
      • 1970-01-01
      • 2010-12-13
      相关资源
      最近更新 更多