我在使用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
个文件中,但是这个问题中提到的测试都是在我的开发机器上运行的{{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)
答案 0 :(得分:5)
我遇到了同样的问题,使用Apache POI阅读庞大的.xlsx文件,我遇到了
此库充当流式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
,它将把数据推送到某个数据库,在那里可以轻松处理,汇总等。