【问题标题】:Apache POI much quicker using HSSF than XSSF - what next?Apache POI 使用 HSSF 比使用 XSSF 快得多——接下来呢?
【发布时间】:2016-01-25 14:31:19
【问题描述】:

我在使用 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。考虑到与处理开始之前相比,这是堆顶部的额外演出,这肯定是太高了?

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

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

原因:java.lang.OutOfMemoryError: Java heap space 在 org.apache.xmlbeans.impl.store.Cur.createElementXobj(Cur.java:260) 在 org.apache.xmlbeans.impl.store.Cur$CurLoadContext.startElement(Cur.java:2997) 在 org.apache.xmlbeans.impl.store.Locale$SaxHandler.startElement(Locale.java:3211) 在 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)

【问题讨论】:

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


【解决方案1】:

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

excel-streaming-reader-github

此库用作该流式 API 的包装器,同时保留标准 POI API 的语法

这个库可以帮助你阅读大文件。

【讨论】:

  • 谢谢,这看起来正是我需要的东西!可惜这些问题没有记录在案。
  • 因为这似乎是解决一个常见问题的最佳解决方案(尽管我可能不得不分叉它以使其与应用程序引擎一起使用),所以你得到了战利品 :)
【解决方案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,它将数据推送到某个数据库,在那里可以轻松地处理、聚合等。

【讨论】:

  • 我非常感谢这个答案,并且您分享了您的代码。似乎有很多解决方案可以解决相同的问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-10
  • 1970-01-01
相关资源
最近更新 更多