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

у меня были некоторые проблемы с парсингом .xlsx файлы с Apache POI-я получаю java.lang.OutOfMemoryError: Java heap space в моем развернутом приложении. Я обрабатываю только файлы под 5MB и около 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 секунд

на часто задаваемые вопросы в частности, говорится:

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

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

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

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

Heap space is surely too high here?

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

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

вызвано: java.ленг.Исключение OutOfMemoryError: Java для кучи пространства в орг.апаш.xmlbeans.impl.магазин.Дворняжка.createElementXobj(Cur.java: 260) в орг.апаш.xmlbeans.impl.магазин.Cur$CurLoadContext.startElement (Cur.java: 2997) в орг.апаш.xmlbeans.impl.магазин.Локали$SaxHandler.startElement (Locale.java: 3211) в орг.апаш.xmlbeans.impl.пикколо.XML.Пикколо.reportStartTag (Piccolo.java: 1082) на орг.апаш.xmlbeans.impl.пикколо.XML.Пиккололексер.parseAttributesNS (PiccoloLexer.java: 1802) в орг.апаш.xmlbeans.impl.пикколо.XML.Пиккололексер.parseOpenTagNS (PiccoloLexer.java: 1521)

2 ответов


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

excel-streaming-reader-github

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

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


средний лист XLSX, который я работаю, составляет около 18-22 листов 750 000 строк с 13-20 столбцами. Это вращается в весеннем веб-приложении с множеством других функций. Я дал всему приложению не так много памяти:-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, который будет толкать данные в некоторую базу данных, где их можно легко обрабатывать, агрегировать и т. д.