Java 将POI工作簿流式传输到servlet输出流

Java 将POI工作簿流式传输到servlet输出流,java,servlets,apache-poi,Java,Servlets,Apache Poi,我在我的web服务器上构建了一个非常大的POI工作簿。将整个工作簿保存在内存中,将无法扩展到多个并发请求。是否有一种方法可以逐步将工作簿写入servlet输出流。这将减少响应时间,同时提高进程内存的效率。不幸的是,在没有顺序数据的情况下,这是不可能的。我建议寻找另一种格式,例如CSV或XML。两者都可以按顺序写出。如果它来自一个数据库,它甚至可以做得更有效,因为一个像样的数据库有内置的设施,可以高效地导出到这些格式。您只需将字节从一端流到另一端。如果您使用 它有示例代码,用于向Servlet读取

我在我的web服务器上构建了一个非常大的POI工作簿。将整个工作簿保存在内存中,将无法扩展到多个并发请求。是否有一种方法可以逐步将工作簿写入servlet输出流。这将减少响应时间,同时提高进程内存的效率。

不幸的是,在没有顺序数据的情况下,这是不可能的。我建议寻找另一种格式,例如CSV或XML。两者都可以按顺序写出。如果它来自一个数据库,它甚至可以做得更有效,因为一个像样的数据库有内置的设施,可以高效地导出到这些格式。您只需将字节从一端流到另一端。

如果您使用 它有示例代码,用于向Servlet读取流代码和从Servlet读取流代码。

此API的唯一缺点似乎是它最多只支持Excel 2003


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

如果您要生成Excel 2007(xslx),则可以采用BigGridDemo.java的方法,如下所述:


解决方案是让POI生成一个仅作为模板的容器xslx,并将实际的电子表格数据作为XML流传输到zip输出流中。优化XML生成则取决于您。

您是否尝试过将write方法直接写入HttpServletResponse.getOutputStream()

请看以下示例:

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

自从编写了其余的答案以来,情况已经有了很大的改善——流媒体现在是Apache Poi的一部分

看这个班,还有这个。它在工作表上使用流式窗口,将窗口外的旧行刷新为临时文件

这是基于中使用的
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();
}
publicstaticvoidmain(String[]args)抛出Throwable{
//在内存中保留100行,超过的行将刷新到磁盘
SXSSFWorkbook wb=新SXSSFWorkbook(100);
Sheet sh=wb.createSheet();
对于(int-rownum=0;rownum<1000;rownum++){
Row Row=sh.createRow(rownum);
对于(int-cellnum=0;cellnum<10;cellnum++){
Cell Cell=row.createCell(cellnum);
字符串地址=新单元格引用(cell.formataString();
cell.setCellValue(地址);
}
}
//rownum<900的行被刷新且不可访问
对于(int-rownum=0;rownum<900;rownum++){
Assert.assertNull(sh.getRow(rownum));
}
//最后100行仍在内存中
对于(int-rownum=900;rownum<1000;rownum++){
Assert.assertNotNull(sh.getRow(rownum));
}
FileOutputStream out=新的FileOutputStream(“/temp/sxssf.xlsx”);
wb.写(出);
out.close();
//处置磁盘上支持此工作簿的临时文件
wb.dispose();
}

我正在将POI工作簿写入servlet outputstream。幕后发生的事情是,它将字节写入输出流。我的问题是,我不知道如何循序渐进地写。我必须等到整个工作簿创建完毕,然后才将其写入I/O。写入I/O大约需要一分钟。不知道这是否合理。您的电子表格有多大?您是创建电子表格还是将其提供给您?如果它给了你,你会修改它吗?为什么要花这么多时间写入I/O?-1这并不能回答问题。OP明确指出,他试图解决的问题是如何避免创建所有内容,然后一次全部写出。-1您不能流式传输XLX。就像上面提到的OP一样,在将所有行写入WB之前,此示例似乎仍将整个工作簿保留在内存中,然后再将其流式输出。有趣的是,wb.write(out)调用是否可以放入for循环以解决初始问题。问题不仅在于内存,有时还在于准备整个文档所需的时间。在数据可用时开始流式传输数据似乎是合理的,而不是先等待所有数据。@SergeyShcherbakov感谢您的评论,但恐怕这是不正确的
SXSSFWorkbook
在后台将临时数据刷新到磁盘,因此本例不会将整个工作簿保存在内存中。临时格式是必需的,因为除非您首先知道整个文档(它具有摘要信息和转发引用),否则xlsx格式无法作为流写入-此临时格式由
SXSSFWorkbook
处理。有关更多信息,请参阅课堂文档。我已经成功地实现了将一个非常大的Excel文档流式传输到web客户机,使用了这里的获奖答案中的方法。没有过多的内存,没有刷新到磁盘,客户端上没有TTFB超时。这就像一个魅力,是目前为止最好的答案,因为这种方法既不需要过多的内存,也不需要刷新到磁盘上的主要内容,也不会导致客户端上的TTFB超时。无需提前了解全部文件内容。