EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
@Data
public class User{
@ExcelProperty(value = "名称", index = 0)
@ColumnWidth(value = 15)
private String name;//名称
@ExcelProperty(value = "年龄", index = 1)
@ColumnWidth(value = 15)
private String age;//患者姓名
@ExcelProperty(value = "性别", index = 2)
@ColumnWidth(value = 15)
private String sex;//患者id
@ExcelProperty(value = "创建时间", index = 4)
@ColumnWidth(value = 15)
private String confirmDate;//创建时间
}
读取excle文件可以分为两种方式,一种是异步读取,另一种是同步读取
异步读取首先要构建监听器继承AnalysisEventListener
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
public class ExcelListener<T> extends AnalysisEventListener<T> {
private List<T> list=new ArrayList<>();
//读取每一行数据执行一次
@Override
public void invoke(T t, AnalysisContext analysisContext) {
list.add(t);
}
//读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头:"+headMap);
}
//数据全部读取完毕
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
save(list);
System.out.println("所有数据解析完毕.................");
}
public void save(List<T> list){
//逻辑代码
}
}
读取代码
public class ExcelUtils {
/**
* 读取excel文件
* @param file 文件
* @return List
*/
public static void readExcel(File file){
InputStream ins=null;
try {
ins=new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//sheet 设置处理的工作簿 headRowNumber 设置从excel第几行开始读取
EasyExcel.read(ins, User.class ,new ExcelListener<User>()).sheet().headRowNumber(1).doRead();//第0行一般是表头,从第1行开始读取
}
}
public class Test{
public static void main(String[] args) {
ExcelUtils readExcel(new File("D:\\test.xlsx"),User.class);
}
}
同步读取可以不需要上面的监听器了(不过如果需要验证表头的话,还是可以加上监听器来验证表头信息)
其实也就是 一个使用 doRead() 另一个使用 doReadSync()
public class ExcelUtils {
/**
* 读取excel文件
* @param file 文件
* @return List
*/
public static List readExcel(File file,Class clazz){
InputStream ins=null;
try {
ins=new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//sheet 设置处理的工作簿 headRowNumber 设置从excel第几行开始读取
return EasyExcel.read(ins).head(clazz).sheet().headRowNumber(1).doReadSync();//第0行一般是表头,从第1行开始读取
}
1.在exlcel中有明明有数据,但是读取进来后对象属性全是空
有可能是使用了 注解 @Accessors(chain = true) 这个注解会和 EasyExcel 有冲突,导致数据无法读取进来,一些高版本好像已经修复了此BUG
2.在读取日期形式的数据时,可能会造成精度丢失 比如:
这样的 yyyy-MM-dd HH:mm:ss 数据读取进来后可能会 变成 yyyy-MM-dd HH:mm
也就是这样
这种情况可以在实体工具类中增加一个注解,来让EasyExcel 知道读取日期的格式
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "创建时间", index = 4)
@ColumnWidth(value = 15)
private String confirmDate;//创建时间
这样读取进来的日期就会正常了
3.在目前的 easyExcel 中有一些 BUG ,如果 excel 第一行没有内容,是不会触发 invokeHeadMap 的方法的
也就是说如果你传入的 excel 文件第一行没有内容,那么你写在 invokeHeadMap 方法中的表头验证方法是不会被触发的
目前有一个办法是在 invoke 中写一些代码进行判断 如下:
private boolean isModel=true;
@Override
public void invoke(Object o, AnalysisContext analysisContext) {
//非正常表格式验证,第一行没数据
if(isModel){
throw new SystemException("请使用规定模板查询");
}
}
@Override
public void invokeHeadMap(Map headMap, AnalysisContext context) {
if(isModel){
isModel=false;
}
..........//其他验证表头代码
}
public class ExcelUtils {
/**
* 像浏览器输出excel文件
* @param response HttpServletResponse
* @param data 输出的数据
* @param fileName 输出的文件名称 excel的名称
* @param sheetName 输出的excel的sheet的名称 也就是页的名称
* @param clazz 输出数据的模板
*/
public static void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName, String sheetName, Class clazz){
//表头样式
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
//设置表头居中对齐
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
//内容样式
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置内容靠左对齐
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
try {
EasyExcel.write(getOutputStream(fileName, response), clazz).excelType(ExcelTypeEnum.XLSX).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy).doWrite(data);
} catch (Exception e) {
throw new SystemException("输出excel文件失败", e);
}
}
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
return response.getOutputStream();
}
}
@GetMapping("/test/downloadExcel")
public static void test(){
List<User> userList=new Arraylist<>();
//省略添加数据代码
try {
ExcelUtil.writeExcel(response, userList, fileName, sheetName, UserExcel.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public class ExcelUtils {
/*
* @Author: pang.jialei
* @Description: 读取excle模板,将数据输出到模板
* @Date: 2021/6/3 15:04
* @Param: templateInputStream: 模板所在的地址
* @param tempFileName: 生成的excel文件名称
* @param downloadDir: 输出excel文件的地址
* @param parms: list表格数据
* @param mapData: 其他参数
**/
public static void invoke(InputStream templateInputStream, String tempFileName, String downloadDir, List<?> parms, Map<String, Object> mapData) {
File file = new File(downloadDir);
if (!file.exists()) {
file.mkdirs();
}
String tempFile = downloadDir + File.separator + tempFileName;
ExcelWriter excelWriter = EasyExcel.write(tempFile).withTemplate(templateInputStream).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
// 如果数据量大 list不是最后一行 参照下一个
if (parms != null) {
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(parms, fillConfig, writeSheet);
}
if (mapData != null) {
excelWriter.fill(mapData, writeSheet);
}
excelWriter.finish();
}
/*
* @Author: pang.jialei
* @Description: 读取excle模板,将数据输出到多sheet模板
* @Date: 2021/6/3 15:04
* @Param: templateInputStream: 模板所在的地址
* @param tempFileName: 生成的excel文件名称
* @param downloadDir: 输出excel文件的地址
* @param listMapData: 的表格数据
* @param sheetNames: sheet名称
* @param mapData: > 其他参数
**/
public static void invokeListSheet(InputStream templateInputStream, String tempFileName, String downloadDir, Map<String,List<?>> listMapData, List<String> sheetNames, Map<String,Map<String, Object>> mapData) {
File file = new File(downloadDir);
if (!file.exists()) {
file.mkdirs();
}
String tempFile = downloadDir + File.separator + tempFileName;
ExcelWriter excelWriter = EasyExcel.write(tempFile).withTemplate(templateInputStream).build();
for (String sheet:sheetNames) {
WriteSheet writeSheet = EasyExcel.writerSheet(sheet).build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。
// forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用
// 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存
// 如果数据量大 list不是最后一行 参照下一个
List parms= listMapData.get(sheet);
if (parms != null) {
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(parms, fillConfig, writeSheet);
}
if (mapData != null) {
excelWriter.fill(mapData.get(sheet), writeSheet);
}
}
excelWriter.finish();
}