今天忙完,上面派发了一个任务,有个项目的导出接口数据量太大了,导出直接内存溢出(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都不会出现内存溢出,并且它在上层做了模型转换的封装,让使用者更简单。
64M内存1分钟内读取75M(46W行25列)的Excel,以上来源于官网↑↑
Github 开源地址:GitHub地址
语雀案例地址:官网案例
导入Maven依赖 ps:这里只做导出数据示范,更多操作请参考官网
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>2.2.4version>
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;
}
简单了解完EasyExcel的用法,就可以把它整合到我们的项目中了,先简单说说我项目中导出的数据为什么会内存溢出(OOM)
其实导出的数据行也就4000多行,但是每行的列多达78列,别问我为什么会这么多,所以行数到达3000上以上, 大概率会出现内存溢出
我们公司框架封装了POI的一套用法,我现在要做的就是把用POI导出的用法替换为EasyExcel
@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
private List<EmergencyeventData> EasyExcelData(List<Map<String, String>>){
if(SysFun.isNotEmpty(data.get("endTimes"))){
eventData.setDocName(data.get("endTimes").toString());
}
emergencyeventDataList.add(eventData);
}
项目是前后端分离,所以接口返回给前端的应该是文件流的形式,这里给出我的示例:
//设置响应格式
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更容易上手,性能更好,关于文中的不足欢迎指出!!写博客不是为了写博客而去写博客,分享一点自己遇到的问题,能够帮助大家的博客才有意义。