[英]Apache POI much quicker using HSSF than XSSF - what next?
我在使用Apache POI解析.xlsx
文件時遇到了一些問題-我在部署的應用程序中遇到了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秒
常見問題解答專門說明:
如果您在3秒內無法在所有HSSF,XSSF和SXSSF中使用50,000行和50列來運行它(最好少得多!),那么問題就出在您的環境上。
接下來,它說運行我完成的XLS2CSV.java
。 饋送上面生成的XSSF文件(具有50000行和50列)大約需要15秒,與寫入文件所花費的時間相同。
我的環境有問題嗎?如果是,我該如何進一步調查?
VisualVM的統計數據顯示,處理期間使用的堆內存高達1.2Gb。 考慮到與處理開始之前相比,堆頂部還有額外的演出,這肯定太高了嗎?
注意:上面提到的堆空間異常僅在生產環境中(在Google App Engine上)發生,並且僅對.xlsx
文件發生,但是此問題中提到的測試已全部在帶有-Xmx2g
開發計算機上-Xmx2g
。 我希望,如果可以在開發設置中解決此問題,則部署時將使用較少的內存。
來自應用程序引擎的堆棧跟蹤:
引起原因:java.lang.OutOfMemoryError:org.apache.xmlbeans.impl.store.Cur $ CurLoadContext.startElement(org.apache.xmlbeans.impl.store.Cur.createElementXobj(Cur.java:260)處的Java堆空間org.apache.xmlbeans.impl.store.Locale $ SaxHandler.startElement(Locale.java:3211)處的Cur.java:2997)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)
我遇到了使用Apache POI讀取龐大的.xlsx文件的問題,並且遇到了
該庫充當該流API的包裝,同時保留標准POI API的語法
該庫可以幫助您讀取大文件。
我工作的平均XLSX工作表約為18-22張,共750 000行,13-20列。 在Spring Web應用程序中,它具有許多其他功能。 我沒有為整個應用程序-Xms1024m -Xmx4096m
太多的內存: -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
,該方法會將數據推送到某個數據庫,在該數據庫中可以輕松對其進行處理,聚合等。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.