easyexcel解决poi大文件发生OOM问题
问题复现
工作中,项目里的导入功能采用了poi读取然后进行业务操作,在导入50M文件时发生了OOM报错信息,以下是本地复现的错误信息(由于环境不一样,本地导入14M的文件就已出现错误)
究其原因
项目中使用WorkBook这个类处理文件,这会先把文件中cell读到内存当中,当数据量比较大的时候就会产生java.lang.OutOfMemoryError: Java heap space错误。
看一下导入过程中内存的消耗情况
可以看出,导入过程内存消耗越来越大,直至抛出异常
easyExcel解决
核心原理
easyExcel是阿里的一个开源项目,可以解决内存消耗大的问题,主要因为解决了3个问题:
1.文件解压和读取的方式是通过文件的形式,不再是poi的内存形式
2.使用sax模式一行一行的读取,不将数据全部加载到内存中
3.抛弃了不重要的数据,比如字体宽度大小等格式。
性能测试
以上都是官网上的说法,不能只听他吹,实际测试一下到底读取大文件时会不会内存溢出,为了简单,只是读取数据,并没有业务逻辑
代码准备
@RestController@RequestMapping("easyExcel")
public class TempEasyExcelController {
private static final Logger logger = LoggerFactory.getLogger(EasyExcelController.class);
@Autowired
private TempEasyExcelService easyExcelService;
@PostMapping("impExcel")
public String importExcel(MultipartFile file) {
long start = System.currentTimeMillis();
try {
easyExcelService.importExcel(file.getInputStream());
logger.info("文件大小:{},easyExcel共耗费:{} ms", (System.currentTimeMillis() - start));
return "good";
} catch (Exception e) {
e.printStackTrace();
return "no good";
}
}
@PostMapping("impPoi")
public String impPoi(MultipartFile file) {
long start = System.currentTimeMillis();
try {
easyExcelService.poiTest(file.getInputStream());
logger.info("poi共耗费:{} ms", (System.currentTimeMillis() - start));
return "good";
} catch (Exception e) {
e.printStackTrace();
return "no good";
}
}
}
@Servicepublic class TempEasyExcelService {
private static final Logger logger = LoggerFactory.getLogger(EasyExcelService.class);
private static final Executor executor = Executors.newFixedThreadPool(10, r -> {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
});
@Autowired
private TempEasyExcelMapper easyExcelMapper;
public void importExcel(InputStream inputStream) {
TempEasyExcelListtener easyExcelListtener = new TempEasyExcelListtener(easyExcelMapper);
EasyExcelFactory.read(inputStream, EasyExcelTestEntity.class, easyExcelListtener)
.doReadAll();
}
public void poiTest(InputStream inputStream) throws Exception {
long start = System.currentTimeMillis();
Workbook book = new XSSFWorkbook(inputStream);
Sheet sheet = book.getSheetAt(0);
List<EasyExcelTestEntity> list = new ArrayList<>();
for (int i = 0; i < sheet.getLastRowNum(); i++) {
EasyExcelTestEntity excel = createExcel(sheet.getRow(i));
list.add(excel);
}
logger.info("poi读取耗费:{} ms", (System.currentTimeMillis() - start));
}
private EasyExcelTestEntity createExcel(Row row) {
EasyExcelTestEntity easyExcelTestEntity = new EasyExcelTestEntity();
easyExcelTestEntity.setId(UUID.getUUID());
easyExcelTestEntity.setS(getCellStr(row, 0));
easyExcelTestEntity.setS1(getCellStr(row, 1));
easyExcelTestEntity.setS2(getCellStr(row, 2));
easyExcelTestEntity.setS3(getCellStr(row, 3));
easyExcelTestEntity.setS4(getCellStr(row, 4));
easyExcelTestEntity.setS5(getCellStr(row, 5));
easyExcelTestEntity.setS6(getCellStr(row, 6));
easyExcelTestEntity.setS7(getCellStr(row, 7));
easyExcelTestEntity.setS9(getCellStr(row, 8));
easyExcelTestEntity.setS10(getCellStr(row, 9));
easyExcelTestEntity.setS11(getCellStr(row, 10));
easyExcelTestEntity.setS12(getCellStr(row, 11));
easyExcelTestEntity.setS13(getCellStr(row, 12));
easyExcelTestEntity.setS14(getCellStr(row, 13));
easyExcelTestEntity.setS15(getCellStr(row, 14));
easyExcelTestEntity.setS16(getCellStr(row, 15));
easyExcelTestEntity.setS17(getCellStr(row, 16));
return easyExcelTestEntity;
}
protected String getCellStr(Row row, int col) {
Cell cell = row.getCell(col);
if (cell == null) {
return "";
}
return cell.getStringCellValue();
}
}
//监听器
public class TempEasyExcelListtener extends AnalysisEventListener<EasyExcelTestEntity> {
private List<EasyExcelTestEntity> list = new ArrayList<>();
public TempEasyExcelListtener(TempEasyExcelMapper easyExcelMapper) {
}
@Override
public void invoke(EasyExcelTestEntity easyExcelTestEntity, AnalysisContext analysisContext) {
easyExcelTestEntity.setId(UUID.getUUID());
list.add(easyExcelTestEntity);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println(analysisContext.readSheetHolder().getSheetName() + ":" + list.size());
list.clear();
}
}
导入结果
由于项目中文件都比较大,先传一个85M的文件,过程中无报错信息
执行成功,看来有点靠谱
ps:具体easyExcel的用法可以看GitHub的文档,有许多很不错的功能,也可自定义拦截器,添加自己想要的 https://alibaba-easyexcel.github.io/quickstart/faq.html
运用到工作中
easyExcel的写法还算简单,只是和之前的poi写法大都不同,在转换业务模型上还是费了一些功夫,不过效果还是不错的,具体的业务不说,只说一点
由于业务需要,我的监听器并没有使用具体的实体类接收,而是使用Map<Integer, CellData> data,这里有个小问题:如果文件时xls类型的,我把data一个个放进一个map中
Map<Integer, Map<Integer, CellData>> dataMap ,假如有10条数据,那么这些数据全部一样,xlsx正常
所以需要针对xls文件作出处理,这个小问题已经反馈给负责人,下次版本就会更新了。
另外,为了防止出现超大对象,每读取100行,处理一次业务
其他的倒也没什么了,需要根据自己的业务,选取不同的接收对象
测试
前面做了一个简单的测试,那么正式项目中会怎么样呢,使用jprofiler监测内存状况
首先是项目启动完成
开始导入文件,导入一个53M的文件,导入时间比较长,截取一段
之所以会有这样的锯齿形,是因为我每100条就去处理,然后把数据清空
总结
easyExcel的性能主要体现在 读文件上面,确实有效的避免了OOM,而且相对于poi的sax模式,代码更加简单。虽然还是有点小问题,比如前面提到的地址值一样的问题,而且目前对日期的转换支持还不是很好等等,另外easyExcel在写文件这一方面,性能上并没有提升,仅仅是封装了poi,但是代码比较简单,配合实体类上的各种注解,简单的写,包括一些合并,代码一两行就可以实现,不用像poi那么繁琐。但是对于一些稍微复杂点的文件格式,代码写起来还是比较麻烦。不过总体还是不错的,毕竟解决了核心问题。
以上是 easyexcel解决poi大文件发生OOM问题 的全部内容, 来源链接: utcz.com/z/511990.html