Apache POI гораздо быстрее использует HSSF, чем XSSF - что дальше?

У меня возникли проблемы с разбором файлов .xlsx с Apache POI - я получаю java.lang.OutOfMemoryError: Java heap space в моем развернутом приложении. Я обрабатываю файлы только под 5 Мбайт и около 70 000 строк, поэтому мое подозрение от чтения других вопросов заключается в том, что что-то не так.

Как было предложено в этом комментарии, я решил запустить SSPerformanceTest.java с предлагаемыми переменными, чтобы посмотреть, нет ли чего-то неправильного в моем коде или настройке. Результаты показывают значительное различие между HSSF (.xls) и XSSF (.xlsx):

1) HSSF 50000 50 1: Истек 1 секунда

2) SXSSF 50000 50 1: Истекшее 5 секунд

3) XSSF 50000 50 1: Истекшее 15 секунд

FAQ специально говорит:

Если вы не можете запустить это с 50 000 строк и 50 столбцов во всех HSSF, XSSF и SXSSF менее чем за 3 секунды (в идеале намного меньше!), проблема связана с вашей средой.

Далее, он говорит, чтобы выполнить XLS2CSV.java, который я сделал. Подача в файл XSSF, сгенерированный выше (с 50000 строк и 50 столбцов), занимает около 15 секунд - столько же, сколько требуется для записи файла.

Что-то не так с моей средой, и если да, то как я буду исследовать дальше?

Статистика из VisualVM показывает, что куча использовала съемку до 1,2 ГБ во время обработки. Наверняка, это слишком высоко, учитывая, что дополнительный концерт на вершине кучи по сравнению с началом обработки начался?

Здесь пустое место здесь?

Примечание. Указанное выше исключение кучного пространства происходит только в процессе производства (в Google App Engine) и только для файлов .xlsx, однако тесты, упомянутые в этом вопросе, были запущены на моей машине разработки с помощью -Xmx2g. Я надеюсь, что если я смогу исправить проблему в моей настройке разработки, она будет использовать меньше памяти при развертывании.

Трассировка стека из движка приложения:

Вызвано: java.lang.OutOfMemoryError: пространство кучи Java     at org.apache.xmlbeans.impl.store.Cur.createElementXobj(Cur.java:260)     at org.apache.xmlbeans.impl.store.Cur $CurLoadContext.startElement(Cur.java:2997)     at org.apache.xmlbeans.impl.store.Locale $SaxHandler.startElement(Locale.java:3211)     at org.apache.xmlbeans.impl.piccolo.xml.Piccolo.reportStartTag(Piccolo.java:1082)     на org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseAttributesNS(PiccoloLexer.java:1802)     на org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseOpenTagNS(PiccoloLexer.java:1521)

Ответ 1

Я столкнулся с такой же проблемой, чтобы прочитать громоздкий файл .xlsx с помощью Apache POI, и я наткнулся на

excel-streaming-reader-github

Эта библиотека служит оболочкой для этого потокового API, сохраняя при этом синтаксис стандартного API POI

Эта библиотека может помочь вам прочитать большие файлы.

Ответ 2

Средний лист XLSX, который я работаю, составляет около 18-22 листов из 750 000 рядов с 13-20 колонками. Это вращение в веб-приложении Spring с множеством других функций. Я дал всему приложению не столько большую память: -Xms1024m -Xmx4096m - и он отлично работает!

Прежде всего, код сбрасывания: неправильно загружать каждую строку данных в память и запускать ее. В моем случае (отчетность из базы данных PostgreSQL) я переработал процедуру дампа данных, чтобы использовать RowCallbackHandler для записи в мой XLSX, во время этого, когда я достигаю "моего предела" из 750000 строк, я создаю новый лист. И рабочая книга создается с окном видимости в 50 строк. Таким образом, я могу сбрасывать огромные объемы: размер файла XLSX составляет около 1230 МБ.

Некоторый код для написания листов:

    jdbcTemplate.query(
        new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                PreparedStatement statement = connection.prepareStatement(finalQuery, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                statement.setFetchSize(100);
                statement.setFetchDirection(ResultSet.FETCH_FORWARD);
                return statement;
            }
        }, new RowCallbackHandler() {
            Sheet sheet = null;
            int i = 750000;
            int tableId = 0;

            @Override
            public void processRow(ResultSet resultSet) throws SQLException {
                if (i == 750000) {
                    tableId++;
                    i = 0;
                    sheet = wb.createSheet(sheetName.concat(String.format("%02d%n", tableId)));


                    Row r = sheet.createRow(0);

                    Cell c = r.createCell(0);
                    c.setCellValue("id");
                    c = r.createCell(1);
                    c.setCellValue("Дата");
                    c = r.createCell(2);
                    c.setCellValue("Комментарий");
                    c = r.createCell(3);
                    c.setCellValue("Сумма операции");
                    c = r.createCell(4);
                    c.setCellValue("Дебет");
                    c = r.createCell(5);
                    c.setCellValue("Страхователь");
                    c = r.createCell(6);
                    c.setCellValue("Серия договора");
                    c = r.createCell(7);
                    c.setCellValue("Номер договора");
                    c = r.createCell(8);
                    c.setCellValue("Основной агент");
                    c = r.createCell(9);
                    c.setCellValue("Кредит");
                    c = r.createCell(10);
                    c.setCellValue("Программа");
                    c = r.createCell(11);
                    c.setCellValue("Дата начала покрытия");
                    c = r.createCell(12);
                    c.setCellValue("Дата планового окончания покрытия");
                    c = r.createCell(13);
                    c.setCellValue("Периодичность уплаты взносов");
                }
                i++;

                PremiumEntity e = PremiumEntity.builder()
                    .Id(resultSet.getString("id"))
                    .OperationDate(resultSet.getDate("operation_date"))
                    .Comments(resultSet.getString("comments"))
                    .SumOperation(resultSet.getBigDecimal("sum_operation").doubleValue())
                    .DebetAccount(resultSet.getString("debet_account"))
                    .Strahovatelname(resultSet.getString("strahovatelname"))
                    .Seria(resultSet.getString("seria"))
                    .NomPolica(resultSet.getLong("nom_polica"))
                    .Agentname(resultSet.getString("agentname"))
                    .CreditAccount(resultSet.getString("credit_account"))
                    .Program(resultSet.getString("program"))
                    .PoliciStartDate(resultSet.getDate("polici_start_date"))
                    .PoliciPlanEndDate(resultSet.getDate("polici_plan_end_date"))
                    .Periodichn(resultSet.getString("id_periodichn"))
                    .build();

                Row r = sheet.createRow(i);
                Cell c = r.createCell(0);
                c.setCellValue(e.getId());

                if (e.getOperationDate() != null) {
                    c = r.createCell(1);
                    c.setCellStyle(dateStyle);
                    c.setCellValue(e.getOperationDate());
                }

                c = r.createCell(2);
                c.setCellValue(e.getComments());

                c = r.createCell(3);
                c.setCellValue(e.getSumOperation());

                c = r.createCell(4);
                c.setCellValue(e.getDebetAccount());

                c = r.createCell(5);
                c.setCellValue(e.getStrahovatelname());

                c = r.createCell(6);
                c.setCellValue(e.getSeria());

                c = r.createCell(7);
                c.setCellValue(e.getNomPolica());

                c = r.createCell(8);
                c.setCellValue(e.getAgentname());

                c = r.createCell(9);
                c.setCellValue(e.getCreditAccount());

                c = r.createCell(10);
                c.setCellValue(e.getProgram());

                if (e.getPoliciStartDate() != null) {
                    c = r.createCell(11);
                    c.setCellStyle(dateStyle);
                    c.setCellValue(e.getPoliciStartDate());
                }
                ;

                if (e.getPoliciPlanEndDate() != null) {
                    c = r.createCell(12);
                    c.setCellStyle(dateStyle);
                    c.setCellValue(e.getPoliciPlanEndDate());
                }

                c = r.createCell(13);
                c.setCellValue(e.getPeriodichn());
            }
        });

После того, как я переработал свой код при отправке данных в XLSX, я столкнулся с проблемой: для их открытия требуется Office в 64 бита. Поэтому мне нужно разделить мою книгу с большим количеством листов на отдельные файлы XLSX с отдельными листами, чтобы сделать их доступными для чтения на среднем компьютере. И снова я использовал небольшие окна видимости и поточную обработку и поддерживал работоспособность всего приложения без каких-либо взглядов OutOfMemory.

Некоторый код для чтения и разделения листов:

        OPCPackage opcPackage = OPCPackage.open(originalFile, PackageAccess.READ);


        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(opcPackage);
        XSSFReader xssfReader = new XSSFReader(opcPackage);
        StylesTable styles = xssfReader.getStylesTable();
        XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
        int index = 0;
        while (iter.hasNext()) {
            InputStream stream = iter.next();
            String sheetName = iter.getSheetName();

            DataFormatter formatter = new DataFormatter();
            InputSource sheetSource = new InputSource(stream);

            SheetToWorkbookSaver saver = new SheetToWorkbookSaver(sheetName);
            try {
                XMLReader sheetParser = SAXHelper.newXMLReader();
                ContentHandler handler = new XSSFSheetXMLHandler(
                    styles, null, strings, saver, formatter, false);
                sheetParser.setContentHandler(handler);
                sheetParser.parse(sheetSource);
            } catch(ParserConfigurationException e) {
                throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
            }

            stream.close();

            // this creates new File descriptors inside storage
            FileDto partFile = new FileDto("report_".concat(StringUtils.trimToEmpty(sheetName)).concat(".xlsx"));
            File cloneFile = fileStorage.read(partFile);
            FileOutputStream cloneFos = new FileOutputStream(cloneFile);
            saver.getWb().write(cloneFos);
            cloneFos.close();
        }

и

public class SheetToWorkbookSaver implements XSSFSheetXMLHandler.SheetContentsHandler {

    private SXSSFWorkbook wb;
    private Sheet sheet;
    private CellStyle dateStyle ;


    private Row currentRow;

    public SheetToWorkbookSaver(String workbookName) {
        this.wb = new SXSSFWorkbook(50);
        this.dateStyle = this.wb.createCellStyle();
        this.dateStyle.setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat("dd.mm.yyyy"));

        this.sheet = this.wb.createSheet(workbookName);

    }

    @Override
    public void startRow(int rowNum) {
        this.currentRow = this.sheet.createRow(rowNum);
    }

    @Override
    public void endRow(int rowNum) {

    }

    @Override
    public void cell(String cellReference, String formattedValue, XSSFComment comment) {
        int thisCol = (new CellReference(cellReference)).getCol();
        Cell c = this.currentRow.createCell(thisCol);
        c.setCellValue(formattedValue);
        c.setCellComment(comment);
    }

    @Override
    public void headerFooter(String text, boolean isHeader, String tagName) {

    }


    public SXSSFWorkbook getWb() {
        return wb;
    }
}

Таким образом, он считывает и записывает данные. Думаю, в вашем случае вы должны переделать свой код на те же шаблоны: сохранить в памяти только малую площадь данных. Поэтому я бы предложил для чтения создать пользовательский SheetContentsReader, который будет толкать данные в какую-либо базу данных, где ее можно легко обрабатывать, агрегировать и т.д.