2016-01-25 35 views
10

我一直在用Apache POI解析.xlsx文件时出现了一些问题 - 我在我部署的应用程序中获取了java.lang.OutOfMemoryError: Java heap space。我只处理5MB以下的文件和大约70,000行,所以我从阅读中怀疑其他问题的数量是有些不对劲。使用HSSF的Apache POI比XSSF快得多 - 下一步是什么?

正如this comment中所建议的那样,我决定运行SSPerformanceTest.java并提供建议的变量,以查看我的代码或设置是否有任何问题。结果表明HSSF之间和XSSF一个显著差异(.xls)(.xlsx):

1)HSSF 50000 50 1:消逝1秒

2)SXSSF 50000 50 1:消逝5秒

3)XSSF 50000 50 1:消逝15秒

FAQ具体说:

If you can't run that with 50,000 rows and 50 columns in all of HSSF, XSSF and SXSSF in under 3 seconds (ideally a lot less!), the problem is with your environment.

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

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

来自VisualVM的统计数据显示,在处理过程中堆使用的拍摄量高达1.2Gb。当然这太高了,考虑到这是一堆额外的演出,而不是在处理开始之前呢?

Heap space is surely too high here?

注:以上仅提到的堆空间异常生产(在谷歌App引擎)发生,仅.xlsx文件,但是在这个问题中提到的测试已全部我的开发机器上-Xmx2g运行。我希望如果我能解决我的开发设置中的问题,它将在部署时使用更少的内存。从应用程序引擎

堆栈跟踪:

Caused by: java.lang.OutOfMemoryError: Java heap space 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) at org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseAttributesNS(PiccoloLexer.java:1802) at org.apache.xmlbeans.impl.piccolo.xml.PiccoloLexer.parseOpenTagNS(PiccoloLexer.java:1521)

+0

你并不孤单:http://stackoverflow.com/questions/34246083/apache-poi-performance – raggi

+0

该死的,我在这里读了很多问题,但没有找到那个!非常感谢。似乎这是图书馆的问题,如果邮件列表中的沉默是任何事情都可以解决的话。可能会开始研究解决方法。 – slugmandrew

回答

5

我面临着同样的问题,阅读庞大的.xlsx文件使用Apache POI我穿过

excel-streaming-reader-github

这个库作为围绕流API的包装,同时保留标准的POI API的语法来

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

+0

谢谢,这看起来只是我需要的东西!这是一个耻辱,但这些问题没有记录。 – slugmandrew

+1

因为这看起来像是一个常见问题的最佳解决方案(尽管我可能必须将它分叉以使其与应用程序引擎一起玩),您将获得战利品:) – slugmandrew

2

平均XLSX片我的工作是用13-20柱约18-22片750个000行。 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我的代码后,我来到了问题,它需要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,这将推动数据到一些数据库,在那里它可以很容易地处理,汇总等。

+0

我非常感谢这个答案,并且你分享了你的代码。似乎有很多解决方案来解决同一个问题! – slugmandrew

相关问题