我在我的网络服务器上构建了一个非常大的 POI 工作簿。将整个工作簿保存在内存中,不会针对多个并发请求进行扩展。有没有办法可以逐步将工作簿写入 servlet 输出流。这应该会减少响应时间,并提高进程内存效率。
如果您要生成 Excel 2007 (xslx),那么您可以采用 BigGridDemo.java 的方法,如下所述: http://web.archive.org/web/20110821054135/http://www.developrealers.com /博客/代码/excel
解决方案是让 POI 生成一个容器 xslx 仅作为模板,并将实际电子表格数据作为 XML 流式传输到 zip 输出流中。简化 XML 生成就取决于您了。
自从写完其余答案以来,情况已经有了很大改善 - Streaming 现在是 Apache Poi 的一部分。
请参阅 SXSSFWorkbook 类,以及 此处的文档。它在工作表上使用流式窗口,将窗口外的旧行刷新到临时文件。
这是基于
hlg 的答案中使用的
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();
}
不幸的是,当没有顺序数据时这是不可能的。我建议寻找另一种格式,例如CSV 或 XML。两者都可以按顺序写出。如果它来自数据库,甚至可以更有效地完成,因为一个像样的数据库具有内置设施可以有效地导出到这些格式。您只需将字节从一侧流式传输到另一侧即可。
您是否尝试过直接使用 HttpServletResponse.getOutputStream() 的 write 方法?
请看下面的例子:
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("new sheet");
...
OutputStream out = response.getOutputStream();
wb.write(out);
out.close();
如果您使用JExcel 它具有从 Servlet 读取流代码和从 Servlet 读取流代码的示例代码。 http://jexcelapi.sourceforge.net/resources/faq/
此 API 的唯一缺点是它仅支持 Excel 2003(含)。
使用 POI - 您不能创建文件并将文件的字节提供给 servlet 输出流吗?
从版本 5.0.0 开始,有一个 DeferredSXSSFWorkBook 类可用,它使用生成器函数来处理行。这样您就可以直接流式传输到您的 HTTP 响应。我从 DeferredGeneration.java 示例中获取了下面的代码。该 API 被认为是实验性的。在我的用例中它运行良好。
// ExcelWriter.java
public class ExcelWriter {
public static void write(OutputStream out) throws IOException {
try (DeferredSXSSFWorkbook wb = new DeferredSXSSFWorkbook()) {
DeferredSXSSFSheet sheet1 = wb.createSheet("new sheet");
// cell styles should be created outside the row generator function
CellStyle cellStyle = wb.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);
sheet1.setRowGenerator((ssxSheet) -> {
for (int i = 0; i < 10; i++) {
Row row = ssxSheet.createRow(i);
Cell cell = row.createCell(1);
cell.setCellStyle(cellStyle);
cell.setCellValue("value " + i);
}
});
// wb.write(out);
// writeAvoidingTempFiles was added as an experimental change in POI 5.1.0
wb.writeAvoidingTempFiles(out);
// the dispose call is necessary to ensure temp files are removed
// supposedly writeAvoidingTempFiles doesn't guarantee that temp files are NOT created
wb.dispose();
}
}
}
这就是我在 Quarkus 3.8.3 w/ RestEasy Reactive 中实现它的方式。请注意,这仍然是一个阻塞调用。
// SomeResource.java
@GET
@Path("my-endpoint/xlsx")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@ResponseHeader(name="content-disposition", value="attachment; filename=file.xlsx")
public StreamingOutput getFile() {
return new StreamingOutput() {
@Override
public void write(OutputStream out) throws IOException, WebApplicationException {
ExcelWriter.write(out);
}
};
}