什么?你还在用POI导出数据?EasyExcel解决大数据量导出OOM(内存溢出)

前言

今天忙完,上面派发了一个任务,有个项目的导出接口数据量太大了,导出直接内存溢出(OOM),暂时做法是限制导出的行数,然后让我去研究一下,通过一下午的研究,通过EasyExcel解决了这个问题,并且大幅度提高了映射速度,如下图:
什么?你还在用POI导出数据?EasyExcel解决大数据量导出OOM(内存溢出)

EasyExcel介绍

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目,alibaba旗下的高性能处理Excel工具。在尽可能节约内存的情况下支持读写百M的Excel.

Java解析、生成Excel比较常用的框架有Apache POI、JXL,但是他们都有一个严重的问题就是耗内存,POI有一套SAX模式的API可以一定程度上解决一些内存溢出的问题,但POI还是有一些缺陷

比如:07版本的Excel解压后存储都是在内存中完成的,内存消耗依旧很大。EasyExcel重写了POI对07版本Excel的解析,能够让原本一个3M的Excel用POI SAX需要用到100M左右内存降低到几M,并且再大的Excel都不会出现内存溢出,并且它在上层做了模型转换的封装,让使用者更简单。

什么?你还在用POI导出数据?EasyExcel解决大数据量导出OOM(内存溢出)
64M内存1分钟内读取75M(46W行25列)的Excel,以上来源于官网↑↑

Github 开源地址:GitHub地址
语雀案例地址:官网案例

快速使用

导入Maven依赖 ps:这里只做导出数据示范,更多操作请参考官网

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>2.2.4</version>
</dependency>

实体类(模版对象):

@Data
public class DemoData {
	@ExcelProperty("字符串标题")
	private String string;
	// 这里用string 去接日期才能格式化。我想接收年月日格式
	@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
	@ExcelProperty("日期标题")
	private Date date;
	@ExcelProperty("数字标题")
	//我想接收百分比的数字
	// @NumberFormat("#.##%")
	private Double doubleData;
	//忽略这个字段
	@ExcelIgnore
	private String ignore;
}

测试:

@Test
public void yy(){
  // 写法1
  String fileName = "D:\\Program Files (x86)\\program\\excel\\easyExcel\\" + "simpleWrite.xlsx";
  // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
  EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
private List<DemoData> data() {
  List<DemoData> list = new ArrayList<DemoData>();
  for (int i = 0; i < 10; i++) {
   DemoData data = new DemoData();
   data.setString("字符串" + i);
   data.setDate(new Date());
   data.setDoubleData(0.56);
   list.add(data);
}
   return list;
}

效果展示:
什么?你还在用POI导出数据?EasyExcel解决大数据量导出OOM(内存溢出)

项目实战使用

简单了解完EasyExcel的用法,就可以把它整合到我们的项目中了,先简单说说我项目中导出的数据为什么会内存溢出(OOM)

其实导出的数据行也就4000多行,但是每行的列多达78列,别问我为什么会这么多,所以行数到达3000上以上, 大概率会出现内存溢出
什么?你还在用POI导出数据?EasyExcel解决大数据量导出OOM(内存溢出)
我们公司框架封装了POI的一套用法,我现在要做的就是把用POI导出的用法替换为EasyExcel

1.创建导出POJO模板

@Data
@ContentRowHeight(10)
@HeadRowHeight(15) // 标题长度
@ColumnWidth(15) // 标题宽度
public class EmergencyeventData {
    @ExcelProperty("序号")
    private Integer id;

    @ExcelProperty("项目名称")
    private String projectName;

    @DateTimeFormat("yyyy年MM月dd日 HH:mm:ss")
    @ExcelProperty("录入时间")
    private String createDate;

    @DateTimeFormat("yyyy年MM月dd日 HH:mm:ss")
    @ExcelProperty("日期")
    private String detectionTime;
  }

↑以上省略了74个属性(ps:写完真的累)

原来项目封装的POI用法:↓

     List<ExcelHeader> headers = new ArrayList<>();
    headers.add(new ExcelHeader("序号", true));
    headers.add(new ExcelHeader("项目名称", "projectName", String.class));
    headers.add(new ExcelHeader("录入时间", 1, "createDate", Date.class, "yyyy年MM月dd日 HH:mm:ss"));
    headers.add(new ExcelHeader("日期", "detectionTime", String.class));

原来项目封装POI的用法是通过一个List<Map<String, Object>>进行封装映射数据,通过Map中对应的key来符合↑↑代码中的key,这不重要,现在我只需要把List<Map<String, Object>>中的数据塞到我定义的模板对象集合中即可,如下伪代码所示:

private  List<EmergencyeventData> EasyExcelData(List<Map<String, String>>){ 
if(SysFun.isNotEmpty(data.get("endTimes"))){
    eventData.setDocName(data.get("endTimes").toString());
}
 emergencyeventDataList.add(eventData);
 }

2.响应数据,返回文件流

项目是前后端分离,所以接口返回给前端的应该是文件流的形式,这里给出我的示例:

//设置响应格式
    try {
        ExcelResponse.responseHeader(response, XXX.getName());
    } catch (UnsupportedEncodingException e) {
        return ResultForm.createErrorResultForm(null, e.getMessage());
    }

给出我的响应工具类中的方法代码:

//封装响应流
public static void responseHeader(HttpServletResponse response, String name) throws UnsupportedEncodingException {
    Date d = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    String date = sdf.format(d);
    String fileName = name;
    String encodeName = java.net.URLEncoder.encode(fileName, "UTF-8");
    response.setContentType("application/octet-stream");
    response.setHeader("Access-control-Expose-Headers", "attachment");
    response.setHeader("attachment", encodeName + ".xlsx");
}

写入数据,返回文件流:

// 写入的参数为输出流,模板对象,封装好的集合数据,写完数据,文件流会自动关闭
try {
        EasyExcel.write(response.getOutputStream(), EmergencyeventData.class).sheet("事件").doWrite(emergencyeventData);
    } catch (Exception e) {
        return ResultForm.createErrorResultForm(null, e.getMessage());
    }

以上可以直接使用Swagger-ui直接进行接口测试

结尾

没用过EasyExcel 的一定要去用用,比POI更容易上手,性能更好,关于文中的不足欢迎指出!!写博客不是为了写博客而去写博客,分享一点自己遇到的问题,能够帮助大家的博客才有意义。

上一篇:关于EasyExcel 的一些生成模板,导入导出的使用心得(优化版)


下一篇:Springboot整合easyExcel导入导出Excel