项目中有处理excel文件需求,之前用过poi和jxl,两者处理文档的速度很快,但jxl无法处理07及以上版本的excel,而poi经常出现outofmemory错误,了解到阿里有一个开源的easyexcel可以解决poi中的oom问题,所以在项目中尝试使用easyexcel替代poi。
传送门:easyexcel
在实际使用过程中发现有几个地方有些小问题,一是当07版本excel文件中有多个sheet时,在获取指定的sheet中的数据时,指定sheet的顺序与实际取得的数据相反,如下获取第一个sheet中的数据,但是实际取得的是最后一个sheet的顺序
ExcelReader excelReader = new ExcelReader(new FileInputStream(excel), ExcelTypeEnum.XLSX, null, new ExcelListener());
excelReader.read(new Sheet(1, 1,Staff.class));
在查看插件源码后发现,它在获取sheet数据时,存放sheet的顺序不对,修改com.alibaba.excel.read.SaxAnalyserV07中initSheetSourceList方法中的设置顺序时的代码即可,我这里是通过重新排序来修改顺序的,如下
private void initSheetSourceList() throws IOException, ParserConfigurationException, SAXException {
this.sheetSourceList = new ArrayList();
InputStream workbookXml = new FileInputStream(this.workBookXMLFilePath);
/* 重新获取sheet的个数 */
/*sheetCount=0;
Arrays.asList(new File(this.path+"/xl/worksheets/").listFiles()).stream().forEach((e)->{
if(e.getName().endsWith(".xml"))
sheetCount++;
});*/
XmlParserFactory.parse(workbookXml, new DefaultHandler() {
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
if (qName.toLowerCase(Locale.US).equals("sheet")) {
String name = null;
int id = 0;
for (int i = 0; i < attrs.getLength(); i++) {
if (attrs.getLocalName(i).toLowerCase(Locale.US).equals("name")) {
name = attrs.getValue(i);
} else if (attrs.getLocalName(i).toLowerCase(Locale.US).equals("sheetid")) {
id = Integer.parseInt(attrs.getValue(i));
try {
InputStream inputStream = new FileInputStream(XMLTempFile.getSheetFilePath(path, id));
sheetSourceList.add(new SheetSource(id, name, inputStream));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
});
workbookXml.close();
// Collections.sort(sheetSourceList);
Collections.sort(sheetSourceList, new Comparator() {
@Override
public int compare(SheetSource o1, SheetSource o2) {
return o1.id - o2.id;
}
});
另一个问题是,当获取由easyexcel自己生成的excel文件中的数据时,报错问题,经检查,这是由于在上面的代码中设置id时,原先获取id的字段是"r:id",将这个改为"sheetid"即可,如上述代码。
easyexcel通过在用户文件夹中生成xml临时文件作为中转来获取或生成excel,经测试,完成后并不会删除临时文件,删除方法在com.alibaba.excel.read.SaxAnalyserV07中已定义,即
public void stop() {
FileUtil.deletefile(path);
}
可以在完成(execute中finally处)后调用stop方法删除临时文件,其中在关闭时需要把所有已打开的inputstream全部关闭,源码中并没有对此作处理,导致可能出现文件删除不完全,如下
@Override
protected void execute() {
try {
Sheet sheet = analysisContext.getCurrentSheet();
//设置了查询的sheet时
if (!isAnalysisAllSheets(sheet)) {
//当设置的sheetno为0时,不执行
if (this.sheetSourceList.size() < sheet.getSheetNo() || sheet.getSheetNo() == 0) {
return;
}
//取当前已选择的sheet的数据
InputStream sheetInputStream = this.sheetSourceList.get(sheet.getSheetNo() - 1).getInputStream();
parseXmlSource(sheetInputStream);
//关闭inputstream
sheetInputStream.close();
return;
}
//未设置sheetno时,取所有的数据
int i = 0;
for (SheetSource sheetSource : this.sheetSourceList) {
i++;
this.analysisContext.setCurrentSheet(new Sheet(i));
InputStream sheetInputStream = sheetSource.getInputStream();
parseXmlSource(sheetInputStream);
//关闭inputstream
sheetInputStream.close();
}
} catch (Exception e) {
stop();
throw new ExcelAnalysisException(e);
} finally {
//关闭列表中所有的inputstream
sheetSourceList.stream().forEach((e)->{
try {
if(e.getInputStream()!=null)
e.getInputStream().close();
} catch (IOException e1) {
e1.printStackTrace();
}
});
stop();
}
}
源码中删除文件的代码也要作修改,原来只删除了临时的文件而保留了文件夹,需要修改将文件夹也一并删除,代码在com.alibaba.excel.util.FileUtil.deletefile中,如下
public static void deletefile(String delpath) {
File file = new File(delpath);
// 当且仅当此抽象路径名表示的文件存在且 是一个目录时,返回 true
if (!file.isDirectory()) {
file.delete();
} else if (file.isDirectory()) {
String[] filelist = file.list();
for (int i = 0; i < filelist.length; i++) {
File delfile = new File(delpath + File.separator + filelist[i]);
if (!delfile.isDirectory()) {
delfile.delete();
} else if (delfile.isDirectory()) {
deletefile(delpath + File.separator + filelist[i]);
}
}
file.delete();
}
}
全部修改后重新打包即可。
因为easyexcel是通过SAX模式来处理excel文件的,不需要消耗很大的内存,可以避免poi中经常出现的oom错误,但是经测试,在处理稍大些的文件时,耗时很大,所以是否选用easyexcel还需要做一个取舍。
另人奇怪的是,测试中发现在处理03版本的文件时,easyexcel采用的还是poi的方式,所以处理速度很快,在使用过程中需要注意这一点。