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";

}

}

}

@Service

public 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

回到顶部