首页 文章

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

提问于
浏览
10

我在使用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秒 - 与写入文件所需的数量相同 .

Is something wrong with my environment, and if so how do I investigate further?

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

Heap space is surely too high here?

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

来自app引擎的堆栈跟踪:

引起:java.lang.OutOfMemoryError:org.apache.xmlbeans.impl.store.Cur上的org.apache.xmlbeans.impl.store.Cur.createElementXobj(Cur.java:260)中的Java堆空间$ CurLoadContext.startElement (Cur.java:2997)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)at org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseOpenTagNS(PiccoloLexer.java:1521)

2 回答

  • 2

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

    excel-streaming-reader-github

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

    该库可以帮助您读取大文件 .

  • 5

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

    首先是转储代码:在内存中加载每个数据行并开始转储它是错误的 . 在我的情况下(从PostgreSQL数据库报告)我重写数据转储程序使用 RowCallbackHandler 写入我的XLSX,在此期间我达到750000行的"my limit",我创建新表 . 并且使用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 ,它将数据推送到某个数据库,在那里可以轻松处理,聚合等 .

相关问题