使用HSSF的Apache POI比XSSF快得多 - 下一步是什么?

时间:2016-01-25 14:31:19

标签: java performance google-app-engine apache-poi

我在使用Apache POI解析.xlsx文件时遇到了一些问题 - 我在部署的应用中获得了java.lang.OutOfMemoryError: Java heap space。我只处理5MB以下和大约70,000行的文件,所以我怀疑阅读其他问题是否有问题。

根据this comment的建议,我决定使用建议的变量运行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具体说:

  

如果你不能在3秒内完成所有HSSF,XSSF和SXSSF中50,000行和50列的运行(理想情况下要少得多!),问题在于您的环境。

接下来,它说要运行我已经完成的XLS2CSV.java。在上面生成的XSSF文件(包含50000行和50列)中输入大约需要15秒 - 与写入文件所用的数量相同。

我的环境有问题,如果有,我该如何进一步调查?

VisualVM的统计数据显示在处理过程中使用的堆高达1.2Gb。当然,考虑到与处理开始之前相比,这是一个额外的演出,这太过分了吗?

Heap space is surely too high here?

注意:上面提到的堆空间异常只发生在生产中(在Google App Engine上)并且只发生在.xlsx个文件中,但是这个问题中提到的测试都是在我的开发机器上运行的{{1 }}。我希望如果我可以在我的开发设置中解决问题,那么在部署时它将占用更少的内存。

来自应用引擎的堆栈跟踪:

  

引起:java.lang.OutOfMemoryError:Java堆空间       在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)       在org.apache.xmlbeans.impl.piccolo.xml.Piccolo.reportStartTag(Piccolo.java:1082)       at org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseAttributesNS(PiccoloLexer.java:1802)       在org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseOpenTagNS(PiccoloLexer.java:1521)

2 个答案:

答案 0 :(得分:5)

我遇到了同样的问题,使用Apache POI阅读庞大的.xlsx文件,我遇到了

excel-streaming-reader-github

此库充当流式API的包装器,同时保留标准POI API的语法

此库可以帮助您阅读大文件。

答案 1 :(得分:2)

我工作的平均XLSX纸张大约18-22张750,000行,13-20列。这在具有许多其他功能的Spring Web应用程序中正在旋转。我给了整个应用程序而不是那么多内存:-Xms1024m -Xmx4096m - 而且效果很好!

首先转储代码:在内存中加载每个数据行并开始转储它是错误的。在我的情况下(从PostgreSQL数据库报告)我重写了数据转储过程以使用RowCallbackHandler写入我的XLSX,在此期间,当我达到750000行的“我的限制”时,我创建了新的工作表。并且使用50行的可见性窗口创建工作簿。通过这种方式,我可以转储大量的文件:XLSX文件的大小约为1230Mb。

编写工作表的一些代码:

    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后重新编写代码后,我遇到了问题,它需要64位的Office才能打开它们。因此,我需要将包含大量工作表的工作簿拆分为单独的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,它将把数据推送到某个数据库,在那里可以轻松处理,汇总等。