Как создать файл Excel в памяти и передать его в потоковом режиме?

Я еще не нашел способа сделать следующее:

  1. Чтение очень большого набора данных
  2. Запишите данные в файл Excel (избегайте нехватки памяти JVM, но в идеале не хотите записывать все это на диск)
  3. Загружайте по частям (в моем случае на S3)

Надеюсь сделать все это по частям (что-то прочитать, что-то сгенерировать, что-то написать, что-то повторить). Но я не выяснил, смогу ли я избежать проблем с памятью и избежать записи на диск. Что-то вроде этого может сработать? :

  1. Постепенно читайте данные, используя нумерацию страниц.
  2. Потоковая передача Apache POI выглядит как хороший способ сгенерировать ее в памяти.
  3. Используйте многочастную загрузку AWS S3 для поэтапной передачи.

Но (2) кажется проблемой:

Потоковая передача Apache POI сохраняет в памяти ограниченное количество строк, и это здорово, но конечный результат все равно сбрасывает книгу в файл.

Кто-нибудь делал что-то подобное раньше? (И возможно ли это вообще, учитывая, что Excel представляет собой двоичный формат и представляет собой набор сжатых XML-файлов?)

можете ли вы использовать CSV?

Iłya Bursov 09.05.2024 22:33

Нет, CSV, к сожалению, здесь не вариант.

Bobert 09.05.2024 22:35

двоичный формат? Глядя на API, я вижу, что SXSSFWorkbook управляет XSSFWorkbook, поэтому я ожидаю, что результирующий файл xlsx будет сжатым zip-файлом, содержащим текст XML.

trashgod 10.05.2024 00:13

Да, ты прав. Я еще покопался и обнаружил, что это заархивированные XML-файлы. Это может изменить ситуацию, если сжатие будет... транзитивным? Необходимо проверить, может ли сжатие нескольких файлов по отдельности дать тот же результат, что и сжатие коллекции файлов. Или, я думаю, мне придется заархивировать фрагменты XML, поскольку полная таблица может стать слишком большой. Время для дополнительных исследований.

Bobert 10.05.2024 02:19
DeferredSXSSFWorkbookможет и актуально, но я не пробовала.
trashgod 10.05.2024 03:08

Круто, я такого не видел. Спасибо! Посмотрю.

Bobert 10.05.2024 19:50
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
159
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Эффективная потоковая передача больших документов по мере их создания является распространенной проблемой . SXSSFWorkbook предоставляет хорошую основу, используя модель скользящего окна. На практике поиск оптимального подхода потребует профилирования в конкретном контексте.

Для справки ниже я обновил приведенный пример , чтобы можно было регулировать размер окна. Вариант этого примера , используемый в командной строке, можно использовать для оценки вывода. Кажется, что результат работает без проблем на любом сетевом томе, но результат может зависеть от деталей предложения этого поставщика. Экспериментальная книга DeferredSXSSFWorkbook может оказаться актуальной в будущем; @PJ Fanning подробно рассказывает здесь.


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.junit.Assert;

/**
 * @see https://stackoverflow.com/q/78456843/230513
 */
public class StreamTest {

    private static final int N = 1000;
    private static final int W = 10;

    public static void main(String[] args) throws IOException {
        // keep W rows in memory, N - W rows will be flushed to disk
        var wb = new SXSSFWorkbook(W);
        var sh = wb.createSheet();
        for (int rownum = 0; rownum < N; 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);
            }
        }
        // Verify that W rows before N - W are flushed and inaccessible
        for (int rownum = N - (2 * W); rownum < N - W; rownum++) {
            Assert.assertNull(sh.getRow(rownum));
        }
        // The last W rows are still in memory
        for (int rownum = N - W; rownum < N; rownum++) {
            Assert.assertNotNull(sh.getRow(rownum));
        }
        var file = new File("sxssf.xlsx");
        try (FileOutputStream out = new FileOutputStream(file)) {
            wb.write(out);
        } finally {
            // dispose of temporary files backing this workbook on disk
            wb.dispose();
        }
    }
}
svn.apache.org/repos/asf/poi/trunk/poi-examples/src/main/jav‌​a/… является примером DeferredSXSSF. Вы также можете попробовать метод writeAvoidingTempFiles — он удаляет одно место, где используются временные файлы — SXSSF использует разные временные файлы на разных этапах. DeferredSXSSF с writeAvoidingTempFiles должен использовать наименьшее количество временных файлов (а, возможно, и вообще ни одного)
PJ Fanning 12.05.2024 15:06

Я написал небольшой POC на основе примера DeferredSXSSF и подтвердил, что он не будет записывать временные файлы с помощью writeAvoidingTempFiles(). Но я пока не вижу способа заставить DeferedSDSSF работать с многочастной загрузкой S3. Все происходит автоматически: генерация строк, их сброс на вывод по мере необходимости в зависимости от окна в памяти. Мне нужно было каким-то образом определить, когда минимальная сумма загрузки была записана в выходной поток (или соответствующий PipedInputStream), чтобы вручную загрузитьPart() в S3, верно?

Bobert 22.05.2024 00:21

Извините, я не вижу API-перехватчика для промежуточных результатов. Я вижу апелляцию, но вам, возможно, придется завершить потоковую передачу, прежде чем начинать многочастную загрузку.

trashgod 22.05.2024 01:02

В конечном итоге это сработало для меня очень хорошо. Я написал в PipedOutputStream, сгенерировал лист в основном потоке и попросил другой поток прочитать из соответствующего PipedInputStream, накопить буфер до тех пор, пока он не станет достаточно большим для каждого вызова многочастной загрузки.

Bobert 31.05.2024 01:21

Другие вопросы по теме