利用阿里研发的easyexcel导入导出excel,避免OOM,并对excel加密保护
- 虽然POI是目前使用最多的用来做excel解析的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。
- easyexcel核心原理
写有大量数据的xlsx文件时,POI为我们提供了SXSSFWorkBook类来处理,这个类的处理机制是当内存中的数据条数达到一个极限数量的时候就flush这部分数据,再依次处理余下的数据,这个在大多数场景能够满足需求。
读有大量数据的文件时,使用WorkBook处理就不行了,因为POI对文件是先将文件中的cell读入内存,生成一个树的结构(针对Excel中的每个sheet,使用TreeMap存储sheet中的行)。如果数据量比较大,则同样会产生java.lang.OutOfMemoryError: Java heap space错误。POI官方推荐使用“XSSF and SAX(event API)”方式来解决。
分析清楚POI后要解决OOM有3个关键。
1、1、文件解压文件读取通过文件形式
2、避免将全部全部数据一次加载到内存
采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理。
3、抛弃不重要的数据
Excel解析时候会包含样式,字体,宽度等数据,但这些数据是我们不关心的,如果将这部分数据抛弃可以大大降低内存使用。Excel中数据如下Style占了相当大的空间。 - maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.4</version>
</dependency>
- 导出excel
1、定义导出对象类
package com.jianmin.config;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.Data;
import java.util.Date;
/**
* @Author: jianmin.li
* @Description: 定义学生实体类
* @Date: 2019/8/29 16:24
* @Version: 1.0
*/
@Data
@ColumnWidth(20)
public class Student {
/**
* 将姓名指定为excel的第一列,表头为value
*/
@ExcelProperty(index = 0, value = "姓名")
private String name;
/**
* 将年龄指定为excel的第二列,表头为value
*/
@ExcelProperty(index = 1, value = "年龄")
private Integer age;
/**
* 将生日指定为第三列,表头为value,日期格式指定为'dd/MM/yyyy HH:mm:ss'
*/
@ExcelProperty(index = 2, value = "生日")
@DateTimeFormat("dd/MM/yyyy HH:mm:ss")
private Date birthday;
@Override
public String toString() {
return JSON.toJSONString(this,SerializerFeature.WriteDateUseDateFormat);
}
}
2、导出excel代码示例
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
//设置响应域
response.setContentType("application/octet-stream");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition","attachment;filename=demo.xlsx");
//单元格合并策略-->对第一列每两行进行一次合并
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2,0);
//单元格合并策略-->对第二列每四行进行一次合并
LoopMergeStrategy loopMergeStrategy1 = new LoopMergeStrategy(4,1);
//单元格合并策略-->对第一列到第二列的第一行到第二行进行合并,所有合并策略均不合并表头
OnceAbsoluteMergeStrategy onceAbsoluteMergeStrategy = new OnceAbsoluteMergeStrategy(1,2,0,1);
//构建excelWriter
@Cleanup("finish") ExcelWriter writer = EasyExcel
//将数据映射到Student实体类并响应到浏览器
.write(new BufferedOutputStream(response.getOutputStream()),Student.class)
//自动关闭输出流
.autoCloseStream(Boolean.TRUE)
//excel版本,强烈建议导出07版excel,因为省内存
.excelType(ExcelTypeEnum.XLSX)
//是否需要表头
.needHead(Boolean.TRUE)
//注册单元格合并策略
//.registerWriteHandler(loopMergeStrategy)
//.registerWriteHandler(loopMergeStrategy1)
.registerWriteHandler(onceAbsoluteMergeStrategy)
//设置excel保护密码
.password("123456")
//构建
.build();
//初始化sheet
WriteSheet writeSheet = EasyExcel.writerSheet().build();
writeSheet.setSheetNo(1);
writeSheet.setSheetName("Sheet1");
//获取数据
List<Student> list = getStudents();
//导出excel,注意导出结束要finish
writer.write(list,writeSheet);
}
private List<Student> getStudents() {
List<Student> list = new ArrayList<>();
Student student = new Student();
Student student1 = new Student();
Student student2 = new Student();
Student student3 = new Student();
list.add(student);
list.add(student1);
list.add(student2);
list.add(student3);
student.setAge(25);
student.setBirthday(new Date());
student.setName("王老五");
student1.setAge(26);
student1.setBirthday(new Date());
student1.setName("王老五");
student2.setAge(27);
student2.setBirthday(new Date());
student2.setName("李老六");
student3.setAge(28);
student3.setBirthday(new Date());
student3.setName("李老六");
return list;
}
3、导出结果
导出excel
- 导入excel
1、自定义分析事件监听
package com.jianmin.config;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Author: jianmin.li
* @Description: 自定义分析事件监听
* @Date: 2019/8/29 16:29
* @Version: 1.0
*/
public class ExcelListener extends AnalysisEventListener<Student> {
private final List<Student> list = new ArrayList<>();
/**
* 一行一行的读取数据,将数据放到集合,批量处理
*
* @param student
* @param analysisContext
* @return : void
* @Author: jianmin.li
* @Date: 2019/10/23 9:59
*/
@Override
public void invoke(Student student,AnalysisContext analysisContext) {
list.add(student);
if (list.size() == 2) {
doSomething();
}
}
private void doSomething() {
for (Student student : list) {
System.err.println(student);
}
list.clear();
}
/**
* 数据读取完进行的操作
*
* @param analysisContext
* @return : void
* @Author: jianmin.li
* @Date: 2019/10/23 10:05
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if (list.size() > 0) {
doSomething();
}
}
/**
* 获取表头信息
*
* @param headMap
* @param context
* @return : void
* @Author: jianmin.li
* @Date: 2019/10/23 10:06
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap,AnalysisContext context) {
System.err.println(JSON.toJSONString(headMap));
Integer sheetNo = context.readSheetHolder().getReadSheet().getSheetNo();
System.err.println(sheetNo);
}
}
2、excel导入示例
@PostMapping("/upload")
public void upload(MultipartFile file) throws IOException {
EasyExcel
//将数据映射到Student实体类,并由自定义的分析事件监听处理数据
.read(new BufferedInputStream(file.getInputStream()),Student.class,new ExcelListener())
//表头最大行数
.headRowNumber(1)
//是否对单元格内容自动去除两边的空格
.autoTrim(Boolean.TRUE)
//是否自动关闭输入流
.autoCloseStream(Boolean.TRUE)
//excel保护密码
.password("123456")
//开始导入
.doReadAll();
}
3、导入结果
excel导入