Лениво сгенерировать документ Excel на Java кусками - PullRequest
0 голосов
/ 25 октября 2018

Я ищу способ генерировать очень большие документы 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 не является обязательным требованием, и я с удовольствием переключаюсь на другую библиотеку, если это необходимо.

...