首页 > 解决方案 > 在 Java 中以块的形式懒惰地生成 Excel 文档

问题描述

我正在寻找一种以流方式生成非常大的 Excel 文档(动态)的方法,而不会在内存中保存太多中间状态(最好也不在磁盘上)。我拥有的是一个惰性数据流Stream<Data>,可能包含数十万个Data对象。我想不断将此数据流转换为写入到OutputStream. 最终目标不是将 Excel 文档写入磁盘,我想将其流式传输到 HTTP 响应。

我尝试过使用Apache POI (4.0.0),但 POI 及其SXSSFWorkbook的问题是您只能写入OutputStream一次!即这不起作用:

OutputStream os = ..
Stream<Data> dataStream = ...
SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
Sheet sh = wb.createSheet();
partition(dataStream, 100)
     .peek((List<Data> data) -> addRow(sh, data))
     .forEach(__ -> wb.write(os));

我在这里尝试做的是将数据流 ( Stream<Data>) 划分为 100 个块,然后调用addRow方法(此处未显示)将数据转换为 Excel 行并将其写入Sheet(称为sh)。wb.write(..)如果不是因为在第二次调用时(即当我们到达第二个块时)抛出异常,这实际上应该可以正常工作:

java.io.IOException: Stream closed
    at java.io.BufferedWriter.ensureOpen(BufferedWriter.java:116)
    at java.io.BufferedWriter.write(BufferedWriter.java:221)
    at java.io.Writer.write(Writer.java:157)
    at org.apache.poi.xssf.streaming.SheetDataWriter.beginRow(SheetDataWriter.java:213)
    at org.apache.poi.xssf.streaming.SheetDataWriter.writeRow(SheetDataWriter.java:203)
    at org.apache.poi.xssf.streaming.SXSSFSheet.flushOneRow(SXSSFSheet.java:1876)
    at org.apache.poi.xssf.streaming.SXSSFSheet.flushRows(SXSSFSheet.java:1851)
    at org.apache.poi.xssf.streaming.SXSSFSheet.flushRows(SXSSFSheet.java:1865)
    at org.apache.poi.xssf.streaming.SXSSFWorkbook.flushSheets(SXSSFWorkbook.java:949)
    at org.apache.poi.xssf.streaming.SXSSFWorkbook.write(SXSSFWorkbook.java:923)

我尝试了各种黑客攻击,例如:

OutputStream os = ..
Stream<Data> dataStream = ...
SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
Sheet sh = wb.createSheet();
partition(dataStream, 100)
     .peek((List<Data> data) -> addRow(sh, data))
     .forEach(__ -> {
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    wb.write(byteArrayOutputStream);
                    outputStream.write(byteArrayOutputStream.toByteArray());
                });

但这似乎也不起作用。我当然可以做这样的事情:

OutputStream os = ..
Stream<Data> dataStream = ...
SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
Sheet sh = wb.createSheet();
dataStream.forEach(row -> addRow(sh, row));
wb.write(os);

但这种方法的问题在于,在将第一个字节推送到OutputStream. 这意味着OutputStream消费者需要在数据开始流式传输之前不必要地等待很长时间。

所以我的问题是:如何在不等待首先生成整个文档的情况下生成包含大量行的 Excel文档

请注意,Apache POI 不是必需的,如有必要,我很乐意切换到另一个库。

标签: javaexcelapache-poistream-processing

解决方案


推荐阅读