Я ищу способ генерировать очень большие документы Excel (на лету) в потоковом режиме без , хранящим слишком много промежуточного состояния в памяти (и желательно не на диске).У меня есть ленивый поток данных Stream<Data>
, потенциально содержащий сотни тысяч Data
объектов.Я хочу постоянно преобразовывать этот поток данных в строки Excel, записанные в 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);
Но проблема с этим подходом заключается в том, что весь документ Excel создается (и временно сохраняется на диске) до того, как первые байты будут помещены в OutputStream
.Это означает, что потребителю OutputStream
нужно излишне ждать задолго до того, как данные даже начнут потоковую передачу.
Поэтому у меня следующие вопросы: как мне сгенерировать документ Excel с очень большим набором строк порциями не дожидаясь, пока весь документ будет сгенерирован первым?
Обратите внимание, что Apache POI не является обязательным требованием, и я с удовольствием переключаюсь на другую библиотеку, если это необходимо.