360SDN.COM

首页/Java/列表

基于Apache POI解析Excel文件及内存使用分析

来源:最美软件人  2017-10-24 12:25:51    评论:0点击:

 

摘  要: 介绍了基于Apache POI软件包解析Excel文件的用户模式、事件模式,给出了两种模式解析的关键Java代码,指出用户模式易导致内存溢出。测试了两种模式下解析Excel文件内存使用,事件模式内存占用显著减少,特别是解析2007格式(.xlsx)文件内存使用降低90%以上。提出采用POI软件包解析,如果Excel文件较大建议采用基于事件模式解析,以避免内存溢出。

 

关键词:POI,Excel解析,内存溢出,用户模式,事件模式

已刊载在《电脑编程技巧与维护》十二月刊

1 POI用户模式解析较大Excel文件引起内存溢出

某银行企业网银处理较大的Excel文件的异步处理程序在投产后多次出现内存溢出的情况,经系统工程师分析Java进程的heapdump文件,内存溢出与org/apache/poi/xssf/usermodel/XSSFCell对象的大量使用有关,该对象是使用POI用户模式解析Excel文件时使用的,内存溢出的原因定位为用户模式解析Excel文件导致。

2 POI解析Excel文件用户模式的使用方式

2.1 POI用户模式主要类介绍

POI用户模式(user mode API)解析Excel文件的优点是对开发人员使用非常简单。通过org.apache.poi.ss.usermodel包的接口Workbook启动Excel的解析动作[1]。以下是此接口的两个实现类:

HSSFWorkbook :有读写.xls 格式文件的方法,处理2003格式xls文件;XSSFWorkbook :有读写.xlsx格式文件的方法,处理2007格式xlsx文件。

2.2 用户模式关键代码

Workbook workBook = WorkbookFactory.create(new FileInputStream(excelFile));

Sheet readSheet = workBook.getSheetAt(0);

for(int rowIndex = readSheet.getFirstRowNum(); rowIndex <= readSheet.getLastRowNum(); rowIndex++) {

Row row = readSheet.getRow(rowIndex);

for(int cellIndex = 0;cellIndex < row.getLastCellNum();cellIndex++){

Cell cell = row.getCell(cellIndex);

String cellContent = cell.getStringCellValue();

//处理单元格内容

}

}

2.3 用户模式的缺陷

User API(用户模式)解析时一次性把Excel文件的内容全部读入内存DOM树,因此占用内存较多。如果只需要解析Excel文件中的部分内容,用户模式实际会解析出Excel中的全部内容,处理效率较低。

3 用户模式和事件模式解析Excel的特性比较

除了用户模式外,POI还提供了基于事件模式的Excel解析,采用流方式读取,边读取边进行事件处理,占用内存较少。两种模式解析Excel文件的特性

见下面表1[2]

表1两种模式解析Excel文件的特性

4 事件模式的两套编程模型

具体为HSSF模型(处理2003格式xls文件)、XSSF和SAX事件模型(处理2007格式xlsx文件)。

4.1 HSSF事件模型(处理2003格式xls文件)

通过实现HSSFListener接口对Excel文件中记录的处理来实现解析。实例化HSSFRequest类,并调用HSSFRequest. addListenerForAllRecords ()方法,完成对于事件处理器的注册过程。通过调用HSSFEventFactory.processEvents()来启动读入文件和事件处理。

4.2 使用HSSF解析xls文件关键代码

static Object parseExcelXls(InputStream fin) {

InputStream din = new POIFSFileSystem(fin).createDocumentInputStream("Workbook");

HSSFRequest req = new HSSFRequest();

// 增加EntXlsEventParser对象,处理全部记录的监听器

req.addListenerForAllRecords(new EntXlsEventParser());

new HSSFEventFactory().processEvents(req, din); //调用事件处理

//获取并返回结果

}

class EntXlsEventParser implements HSSFListener {

public void processRecord(Record record) {//处理事件

switch (record.getSid()) {

case BOFRecord.sid:

//表示sheet 或workbook的开始,进行相应的处理

case EOFRecord.sid:

//表示sheet 或workbook的结束,进行相应的处理

case SSTRecord.sid://获取SST表

//获取SST表

case NumberRecord.sid: 

//处理数字和日期记录

case LabelSSTRecord.sid: //处理SST字符串

//根据value中的sst下标,在sst表中提取字符串值

case BoolErrRecord.sid:

//处理Excel错误记录

case FormulaRecord.sid:

//处理公式

}

}

}

4.3 XSSF和SAX事件模型

(处理2007格式xlsx文件)

POI提供了类XSSFSheetXMLHandler基于事件模式解析Excel。使用时需要提供实现SheetContentsHandler接口的类,XSSFSheetXMLHandler调用该类对单元格数据、行数据、页眉页脚数据进行加工和对象拼装,可通过此类来获取最后的返回对象。

4.5使用XSSF和SAX事件模型

解析xlsx文件关键代码

static Object parseExcelXlsx(String filePath) {

OPCPackage opcPack = OPCPackage.open(filePath);

ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(opcPack);

XSSFReader xssfReader = new XSSFReader(opcPack);

StylesTable styles = xssfReader.getStylesTable();

SheetIterator iter = new XSSFReader(opcPack) .getSheetsData();

while (iter.hasNext()) {

XMLReader xmlReader = SAXParserFactory.newInstance().new SAXParser().getXMLReader();

SheetContentsHandler ctHdler = new EntSheetContentsHandler();//自己实现的SheetContentsHandler

DefaultHandler handler = new XSSFSheetXMLHandler(styles, strings, ctHdler, new DataFormatter());

xmlReader.setContentHandler(handler);

xmlReader.parse(new InputSource(iter.next()));//调用SAX事件模式解析

sheetList.add(ctHdler.getSheetData());//通过SheetContentsHandler获取返回对象

}

return sheetList;

}

如果POI提供的XSSFSheetXMLHandler类的解析处理不能满足需求,也可以继承DefaultHandler类通过SAX Parser以事件方式处理XML的相关标签,从而完成xlsx文件的解析。需要实现startElement(一个xml开始标签)、characters(xml开始和结束标签之间的文字)、endElement(xml结束标签)的具体处理。

public class EntXSSFSheetXMLHandler extends DefaultHandler {

//处理XML标签的开始

public void startElement(String uri, String localName, String name, Attributes attributes) {

if (isTextTag(name)) {//判断XML TagName是否表示后面的内容为文本

//设置标记,准备接收文本

} else if ("f".equals(name)) {

//设置标记,准备接收公式

}

else if("row".equals(name)) {

//取得行编号,准备接收行信息

}

else if ("c".equals(name)) {

//为单元格的开始

//根据cellType标记单元格数据类型

//根据cellStyleStr获取数字类型单元格的格式化字符串

}

}

public void characters(char[] ch, int start, int length)

throws SAXException {

//取得XML开始标签和结束标签直接的内容:如字符串、公式等

//一般是放入临时变量中,等待遇到标签结束时再进一步处理

}

//处理XML标签的结束

public void endElement(String uri, String localName, String name) {

if (isTextTag(name)) {//文本类型标签结束

//关闭打开的标记 

//处理各种数据类型的单元格数据

switch (nextDataType) {

case BOOLEAN: 

//处理Boolean单元格信息

case ERROR: 

//处理Error类型单元格信息

case FORMULA: 

//处理公式类型单元格信息

case INLINE_STRING:// 

处理inline字符串

case SST_STRING://处理SST字符串

//根据value中的sst下标,在sst表中提取字符串值

case NUMBER: 

//处理数字和日期类型单元格

}

} else if ("f".equals(name)) {//公式标签结束

//关闭打开的标记

} else if ("is".equals(name)) {//inline string结束

//关闭打开的标记

} else if ("row".equals(name)) {//行结束

//收集行信息

}

}

}

5 测试内存占用情况

5.1 解析Excel文件内存占用测试方法

通过调整Java虚拟机启动时的虚拟机参数-Xmx(Java虚拟机最大堆内存),不断缩小该参数,直至Java应用解析Excel文件时报告OutOfMemoryError。在不报OutOfMemoryError的最小堆内存配置下,Java应用记录解析Excel前后的内存使用量之差,作为该Excel解析的内存占用量。

5.2 解析2007版Excel文件内存使用情况

见图1

图1 两种模式解析2007版Excel文件内存使用情况

5.3 解析2003版Excel文件内存使用情况

见图2

图2 两种模式解析2003版Excel文件内存使用情况

采用基于事件处理的内存占用比采用用户模式占用的内存明显降低,对于2007版(.xlsx)文件新方法内存占用比旧方法降低90%以上。

2007版Excel文件(.xlsx),内部格式是xml等格式的多份文档,经过zip方式压缩后存储[3],由于Excel文件本身经过压缩,解析时程序需要首先解压缩zip文档。因此同样大小的Excel文件,2007版的.xlsx文件解压后比2003版的.xls文件占用空间更大,采用基于事件处理的新方法所带来的内存节省也更为显著。

6 结论与建议

采用事件模式可以解决目前某银行采用用户模式解析Excel文件引起的内存溢出的问题。对于采用Apache POI软件包解析Excel文件的Java应用,如Excel文件较大,建议采用基于事件的模式解析,以避免内存溢出。

参考文献

 

[1] POI教程. http://www.manongjc.com/poi/poi_tutorial.html, 2015.

 

[2] The Apache Software Foundation. Apache POI - the Java API for Microsoft Documents. https://poi.apache.org/index.html, 2016. 

 

[3] Daniel Dick. Spreadsheet Content Overview. http://www.officeopenxml.com/SScontentOverview.php, 2015.

 

撰稿:开发二部  林雪南

编辑:办公室

 

阅读原文

为您推荐

友情链接 |九搜汽车网 |手机ok生活信息网|ok生活信息网|ok微生活
 Powered by www.360SDN.COM   京ICP备11022651号-4 © 2012-2016 版权