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

我在使用Apache POI解析.xlsx文件时遇到了一些问题 – 我收到了java.lang.OutOfMemoryError: Java heap space我部署的应用程序中的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秒

FAQ特别说:

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

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

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

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

这里的堆积空间肯定太高了?

注意:上面提到的堆空间exception只发生在生产中(在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)

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

Excel的流阅读器,github上

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

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

我工作的平均XLSX板材大约是18-22张750,000行,13-20列。 这在具有许多其他function的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 ,它将把数据推送到某个数据库,在那里它可以很容易地处理,聚合等。