ARTICLE · 1074617
EasyExcel:防止解析 Excel 出现 OOM 的神器
EasyExcel:防止解析 Excel 出现 OOM 的神器概述 Java 解析、生成 Excel 比较有名的框架有 Apache POI、JXL,但是他们都存在一个严重的问题、就是非常消耗内存,POI 有一个 SAX 模式的 API 可以一定程度上解决一些内存溢出的问题,但是 POI 还是有一些缺陷,比如 07 版本 Excel 解压缩以及解压后存储都是在内存中完成的,内存消耗依旧很大 EasyExcel 是阿里巴巴开源的基于 Java 的 Excel 处理工具,它重写了 POI 对 07 版本 Excel 的解析,一个 3M 的 Excel 用 POI 的 SAX 模式解析依然需要 100M 左右的内存,改为 EasyExcel 之后可以降低到几 M,并且再大的 Excel 也不会出现内存溢出;03 版本依赖的 POI 的 SAX 模式,在上层做了模型转换的封装,让使用者更加简单方便
官网
坐标
读取示例
读取指定序号的 Sheet 内容 实体类 Excel 文件内容 
测试类 日志打印结果 
读取指定名称的 Sheet 内容 实体类 Excel 文件内容 

测试类 日志打印结果 
表头不在首行的读取 Sheet 内容 实体类 Excel 文件内容 
测试类 日志打印结果 
读取整个 Excel 文件内容 实体类 Excel 文件内容 

测试类 日志打印结果 
未知类型字段的转换 日期时间转换器 实体类 Excel 文件内容 
测试类 日志打印结果 
写入示例
注意
将 POJO 数据写入指定序号的 Sheet 日期时间转换器 实体类 Excel 模板文件 
测试类 Excel 目标文件 
将 POJO 数据写入指定名称的 Sheet 日期时间转换器 实体类 Excel 模板文件 

测试类 Excel 目标文件 
将 Map 数据写入指定序号的 Sheet Excel 模板文件 
测试类 Excel 目标文件 
将 Map 数据写入指定名称的 Sheet Excel 模板文件 

测试类 Excel 目标文件 
将集合数据写入指定序号的 Sheet Excel 模板文件 
日期时间转换器 测试类 Excel 目标文件 
将集合数据写入指定名称的 Sheet Excel 模板文件 

日期时间转换器 测试类 Excel 目标文件 
将 Map 数据、集合数据一起写入指定序号的 Sheet Excel 模板文件 
日期时间转换器 测试类 Excel 目标文件 
将 Map 数据、集合数据一起写入指定名称的 Sheet Excel 模板文件 
日期时间转换器 测试类 Excel 目标文件 
通用基础工具类
https://easyexcel.opensource.alibaba.com/<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><!-- 20240911号最后一次版本号,已经不再维护了 --><version>4.0.3</version></dependency>
/*** 用户信息*/@Getter@Setter@ToStringpublic class UserInfo {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty("生日")private LocalDateTime birthday;@ExcelProperty("地址")private String address;}

/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 获取Excel文件IO输入流File file = Paths.get("D:", "用户信息.xlsx").toFile();// 创建构造器ExcelReaderBuilder builder = EasyExcel.read(file, UserInfo.class, new ReadListener<UserInfo>() {@Overridepublicvoidinvoke(UserInfo data, AnalysisContext context) {System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContext context) {System.out.println("文件读取完毕");}});// 读取第一个Sheet页builder.sheet(0).doRead();}}

/*** 用户信息*/@Getter@Setter@ToStringpublic class UserInfo {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty("生日")private LocalDateTime birthday;@ExcelProperty("地址")private String address;}


/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 获取Excel文件IO输入流File file = Paths.get("D:", "用户信息.xlsx").toFile();// 创建构造器ExcelReaderBuilder builder = EasyExcel.read(file, UserInfo.class, new ReadListener<UserInfo>() {@Overridepublicvoidinvoke(UserInfo data, AnalysisContext context) {System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContext context) {System.out.println("文件读取完毕");}});// 读取指定名称的Sheet页builder.sheet("班级信息").doRead();}}

/*** 用户信息*/@Getter@Setter@ToStringpublic class UserInfo {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty("生日")private LocalDateTime birthday;@ExcelProperty("地址")private String address;}

/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 获取Excel文件IO输入流File file = Paths.get("D:", "用户信息.xlsx").toFile();// 创建构造器ExcelReaderBuilder builder = EasyExcel.read(file, UserInfo.class, new ReadListener<UserInfo>() {@Overridepublicvoidinvoke(UserInfo data, AnalysisContext context) {System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContext context) {System.out.println("文件读取完毕");}});// 读取第一个Sheet页、并且标识第4行为表头builder.sheet(0).headRowNumber(4).doRead();}}

/*** 用户信息*/@Getter@Setter@ToStringpublic class UserInfo {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty("生日")private LocalDateTime birthday;@ExcelProperty("地址")private String address;}


/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 获取Excel文件IO输入流File file = Paths.get("D:", "用户信息.xlsx").toFile();// 创建构造器ExcelReaderBuilder builder = EasyExcel.read(file, UserInfo.class, new ReadListener<UserInfo>() {@Overridepublicvoidinvoke(UserInfo data, AnalysisContext context) {System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContext context) {System.out.println(" Sheet【" + context.readSheetHolder().getSheetName() + "】读取完毕");}});// 读取所有Sheet页builder.doReadAll();}}

/*** 日期时间转换器*/public class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {// 在单元格中存储字符串return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}}
/*** 用户信息*/@Getter@Setter@ToStringpublic class UserInfo {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty(value = "生日", converter = LocalDateTimeConverter.class)private LocalDateTime birthday;@ExcelProperty("地址")private String address;}

/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 获取Excel文件IO输入流File file = Paths.get("D:", "用户信息.xlsx").toFile();// 创建构造器ExcelReaderBuilder builder = EasyExcel.read(file, UserInfo.class, new ReadListener<UserInfo>() {@Overridepublicvoidinvoke(UserInfo data, AnalysisContext context) {System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContext context) {System.out.println("文件读取完毕");}});// 读取第一个Sheetbuilder.sheet(0).doRead();}}

POJO 数据、Map 数据不要内嵌 POJO、Map,EasyExcel 无法实现多层次获取数据并填充,需要通过打平方式来解决该问题 填充集合数据时,必须使用 FillConfig.builder().forceNewRow(Boolean.TRUE).build() 该配置 填充复杂数据时,必须先填充集合数据、再填充其他数据,否则可能会出现数据丢失、排版错误等问题
/*** 日期时间转换器*/public class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {// 在单元格中存储字符串return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}}
/*** 用户信息*/@Getter@Setter@ToStringpublic class UserInfo {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty(value = "生日", converter = LocalDateTimeConverter.class)private LocalDateTime birthday;@ExcelProperty("地址")private String address;}

/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 创建POJOUserInfo userInfo = new UserInfo();userInfo.setName("梦缘");userInfo.setAge(18);userInfo.setBirthday(LocalDateTime.now());userInfo.setAddress("彩虹海");// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();// 写入文件EasyExcel.write(target).withTemplate(template).sheet(0).doFill(userInfo);}}

/*** 日期时间转换器*/public class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {// 在单元格中存储字符串return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}}
/*** 用户信息*/@Getter@Setter@ToStringpublic class UserInfo {@ExcelProperty("姓名")private String name;@ExcelProperty("年龄")private Integer age;@ExcelProperty(value = "生日", converter = LocalDateTimeConverter.class)private LocalDateTime birthday;@ExcelProperty("地址")private String address;}


/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 创建POJOUserInfo userInfo = new UserInfo();userInfo.setName("梦缘");userInfo.setAge(18);userInfo.setBirthday(LocalDateTime.now());userInfo.setAddress("彩虹海");// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();// 写入文件EasyExcel.write(target).withTemplate(template).sheet("班级信息").doFill(userInfo);}}


/*** Excel测试类*/public class ExcelTest {public static void main(String[] args) {// 创建MapMap<String, Object> map = new HashMap<>();map.put("name", "梦缘");map.put("age", 18);map.put("birthday", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));map.put("address", "彩虹海");// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();// 写入文件EasyExcel.write(target).withTemplate(template).sheet(0).doFill(map);}}



/*** Excel测试类*/public class ExcelTest {public static void main(String[] args) {// 创建MapMap<String, Object> map = new HashMap<>();map.put("name", "梦缘");map.put("age", 18);map.put("birthday", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));map.put("address", "彩虹海");// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();// 写入文件EasyExcel.write(target).withTemplate(template).sheet("班级信息").doFill(map);}}


/*** 日期时间转换器*/public class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {// 在单元格中存储字符串return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}}
/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 创建集合List<UserInfo> list = new ArrayList<>();// 填充元素UserInfo userInfo1 = new UserInfo();userInfo1.setName("梦缘");userInfo1.setAge(18);userInfo1.setBirthday(LocalDateTime.now());userInfo1.setAddress("彩虹海");list.add(userInfo1);// 填充元素UserInfo userInfo2 = new UserInfo();userInfo2.setName("本兮");userInfo2.setAge(16);userInfo2.setBirthday(LocalDateTime.now());userInfo2.setAddress("窝窝乡");list.add(userInfo2);// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();// 写入文件EasyExcel.write(target).withTemplate(template).sheet(0).doFill(list, FillConfig.builder().forceNewRow(Boolean.TRUE).build());}}



/*** 日期时间转换器*/public class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {// 在单元格中存储字符串return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}}
/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 创建集合List<UserInfo> list = new ArrayList<>();// 填充元素UserInfo userInfo1 = new UserInfo();userInfo1.setName("梦缘");userInfo1.setAge(18);userInfo1.setBirthday(LocalDateTime.now());userInfo1.setAddress("彩虹海");list.add(userInfo1);// 填充元素UserInfo userInfo2 = new UserInfo();userInfo2.setName("本兮");userInfo2.setAge(16);userInfo2.setBirthday(LocalDateTime.now());userInfo2.setAddress("窝窝乡");list.add(userInfo2);// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();// 写入文件EasyExcel.write(target).withTemplate(template).sheet("班级信息").doFill(list, FillConfig.builder().forceNewRow(Boolean.TRUE).build());}}


/*** 日期时间转换器*/public class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {// 在单元格中存储字符串return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}}
/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 创建集合List<UserInfo> list = new ArrayList<>();// 填充元素UserInfo userInfo1 = new UserInfo();userInfo1.setName("梦缘");userInfo1.setAge(18);userInfo1.setBirthday(LocalDateTime.now());userInfo1.setAddress("彩虹海");list.add(userInfo1);// 填充元素UserInfo userInfo2 = new UserInfo();userInfo2.setName("本兮");userInfo2.setAge(16);userInfo2.setBirthday(LocalDateTime.now());userInfo2.setAddress("窝窝乡");list.add(userInfo2);// 创建MapMap<String, Object> map = new HashMap<>();map.put("total", list.size());// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();try (// 创建Excel写入对象ExcelWriter excelWriter = EasyExcel.write(target).withTemplate(template).build();) {// 创建Sheet对象WriteSheet sheet = EasyExcel.writerSheet(0).build();// 先填充集合数据、后填充普通数据:注意顺序、否则可能出现意料之外的问题,如数据丢失、排版错误等excelWriter.fill(new FillWrapper("infoList", list), FillConfig.builder().forceNewRow(Boolean.TRUE).build(), sheet);excelWriter.fill(map, sheet);}}}


/*** 日期时间转换器*/public class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {// 在单元格中存储字符串return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}}
/*** Excel测试类*/public class ExcelTest {publicstaticvoidmain(String[] args) {// 创建集合List<UserInfo> list = new ArrayList<>();// 填充元素UserInfo userInfo1 = new UserInfo();userInfo1.setName("梦缘");userInfo1.setAge(18);userInfo1.setBirthday(LocalDateTime.now());userInfo1.setAddress("彩虹海");list.add(userInfo1);// 填充元素UserInfo userInfo2 = new UserInfo();userInfo2.setName("本兮");userInfo2.setAge(16);userInfo2.setBirthday(LocalDateTime.now());userInfo2.setAddress("窝窝乡");list.add(userInfo2);// 创建MapMap<String, Object> map = new HashMap<>();map.put("total", list.size());// 模板文件File template = Paths.get("D:", "用户信息.xlsx").toFile();// 填充好内容的文件File target = Paths.get("D:", "目标文件.xlsx").toFile();try (// 创建Excel写入对象ExcelWriter excelWriter = EasyExcel.write(target).withTemplate(template).build();) {// 创建Sheet对象WriteSheet sheet = EasyExcel.writerSheet("班级信息").build();// 先填充集合数据、后填充普通数据:注意顺序、否则可能出现意料之外的问题,如数据丢失、排版错误等excelWriter.fill(new FillWrapper("infoList", list), FillConfig.builder().forceNewRow(Boolean.TRUE).build(), sheet);excelWriter.fill(map, sheet);}}}

package wxw.mengyuan.tool.utils.excel;import com.alibaba.excel.EasyExcel;import com.alibaba.excel.ExcelWriter;import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.converters.Converter;import com.alibaba.excel.converters.ReadConverterContext;import com.alibaba.excel.converters.WriteConverterContext;import com.alibaba.excel.enums.CellDataTypeEnum;import com.alibaba.excel.exception.ExcelAnalysisException;import com.alibaba.excel.exception.ExcelDataConvertException;import com.alibaba.excel.metadata.data.WriteCellData;import com.alibaba.excel.read.builder.ExcelReaderBuilder;import com.alibaba.excel.read.listener.ReadListener;import com.alibaba.excel.write.metadata.WriteSheet;import com.alibaba.excel.write.metadata.fill.FillConfig;import com.alibaba.excel.write.metadata.fill.FillWrapper;import java.io.InputStream;import java.io.OutputStream;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.time.format.DateTimeParseException;import java.util.Collection;import java.util.HashMap;import java.util.Map;import java.util.concurrent.atomic.AtomicBoolean;import java.util.function.BiConsumer;import java.util.function.Supplier;/*** <p>Excel工具类</p>* <p>读取文件</p>* <p>(1)必须使用实体类来映射表头</p>* <p>(2)实体类属性上必须使用注解 @ExcelProperty("列名称") 注解来标注</p>* <p>(3)实体类的属性的类型是 LocalDateTime 等这种非常规类型,可以在创建一个实现 com.alibaba.excel.converters.Converter<T> 的实现类,并且在属性上使用 @ExcelProperty(converter = MyConverter.class) 方式来进行数据转换,例如下方代码中的 LocalDateTimeConverter 自定义转换器</p>* <p>写入文件</p>* <p>(1)使用占位符填充方式来写入,这样避免已有样式被更改</p>* <p>(2)值得注意的第一点:使用占位符方式填充来写入时,若是占位符位置没有对应的数据、则会使用空字符串来填充</p>* <p>(3)值得注意的第二点:传入 Map 来填充时,不能内嵌 Map、POJO,这是无效的,只能通过打平来解决</p>* <p>(4)普通属性在 Excel 文件中对应的占位符的格式为:{name}、{age}</p>* <p>(5)集合属性在 Excel 文件中对应的占位符的格式为:{list_name.name}、{list_name.age}</p>* <p>(6)Excel 模板文件占位符示例</p>* <p> | 名次 | 姓名 | 年龄 | 地址 | </p>* <p> | 第{user.no}名 | {user.name} | {user.age} | {user.address} | </p>* <p> | 总价格:{totalPrice} | </p>*/public final class ExcelUtil implements Converter<String> {/*** 私有化构造方法*/private ExcelUtil() {}/*** 读取某个Sheet页* @param is IO输入流* @param clazz 列的映射类的字节码文件* @param sheetNo Sheet序号,从0开始* @param consumer 消费者*/public static <T> void readSheet(InputStream is, Class<T> clazz, Integer sheetNo, BiConsumer<Integer, T> consumer) {readSheet(is, clazz, sheetNo, null, consumer);}/*** 读取某个Sheet页* @param is IO输入流* @param clazz 列的映射类的字节码文件* @param sheetNo Sheet序号,从0开始* @param rowNumber 数据的开始行号,从0开始,默认为1(不能小于1,因为序号为0的行是用来跟类映射的)* @param consumer 消费者*/public static <T> void readSheet(InputStream is, Class<T> clazz, Integer sheetNo, Integer rowNumber, BiConsumer<Integer, T> consumer) {read(is, clazz, sheetNo, null, rowNumber, consumer);}/*** 读取某个Sheet页* @param is IO输入流* @param clazz 列的映射类的字节码文件* @param sheetName Sheet名称* @param consumer 消费者*/public static <T> void readSheet(InputStream is, Class<T> clazz, String sheetName, BiConsumer<Integer, T> consumer) {readSheet(is, clazz, sheetName, null, consumer);}/*** 读取某个Sheet页* @param is IO输入流* @param clazz 列的映射类的字节码文件* @param sheetName Sheet名称* @param rowNumber 数据的开始行号,从0开始,默认为1(不能小于1,因为序号为0的行是用来跟类映射的)* @param consumer 消费者*/public static <T> void readSheet(InputStream is, Class<T> clazz, String sheetName, Integer rowNumber, BiConsumer<Integer, T> consumer) {read(is, clazz, null, sheetName, rowNumber, consumer);}/*** 读取整个Excel文件* @param is IO输入流* @param clazz 列的映射类的字节码文件* @param consumer 消费者*/public static <T> void readWhole(InputStream is, Class<T> clazz, BiConsumer<Integer, T> consumer) {readWhole(is, clazz, null, consumer);}/*** 读取整个Excel文件* @param is IO输入流* @param clazz 列的映射类的字节码文件* @param rowNumber 数据的开始行号,从0开始,默认为1(不能小于1,因为序号为0的行是用来跟类映射的)* @param consumer 消费者*/public static <T> void readWhole(InputStream is, Class<T> clazz, Integer rowNumber, BiConsumer<Integer, T> consumer) {read(is, clazz, null, null, rowNumber, consumer);}/*** 读取Excel文件* @param is IO输入流* @param clazz 列的映射类的字节码文件* @param sheetNo Sheet序号,从0开始* @param sheetName Sheet名称* @param rowNumber 数据的开始行号,从0开始,默认为1(不能小于1,因为序号为0的行是用来跟类映射的)* @param consumer 消费者*/public static <T> void read(InputStream is, Class<T> clazz, Integer sheetNo, String sheetName, Integer rowNumber, BiConsumer<Integer, T> consumer) {// 判断开始行号if (rowNumber != null) {if (rowNumber < 1) {throw new ExcelAnalysisException("开始行号不能小于1");}}// 创建构造器ExcelReaderBuilder builder = EasyExcel.read(is, clazz, new ReadListener<T>() {@Overridepublic void invoke(T data, AnalysisContext context) {consumer.accept(context.readRowHolder().getRowIndex(), data);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {}});// 从第几行开始读取数据builder.headRowNumber(rowNumber);// 读取哪个Sheet、同时开始读取if (sheetNo == null) {if (sheetName == null) {builder.doReadAll();} else {builder.sheet(sheetName).doRead();}} else {if (sheetName == null) {builder.sheet(sheetNo).doRead();} else {builder.sheet(sheetNo, sheetName).doRead();}}}/*** 写入Excel文件* @param os IO输出流* @param template 模板文件IO输入流* @param sheetNo Sheet序号,从0开始* @param normalMap 普通数据*/public static void writeMap(OutputStream os, InputStream template, Integer sheetNo, Map<String, Object> normalMap) {write(os, template, sheetNo, null, null, normalMap);}/*** 写入Excel文件* @param os IO输出流* @param template 模板文件IO输入流* @param sheetName Sheet名称* @param normalMap 普通数据*/public static void writeMap(OutputStream os, InputStream template, String sheetName, Map<String, Object> normalMap) {write(os, template, null, sheetName, null, normalMap);}/*** 写入Excel文件* @param os IO输出流* @param template 模板文件IO输入流* @param sheetNo Sheet序号,从0开始* @param collection 集合数据*/public static void writeCollection(OutputStream os, InputStream template, Integer sheetNo, Collection<?> collection) {AtomicBoolean hasNext = new AtomicBoolean(true);Map<String, Supplier<Collection<?>>> collectionSupplier = new HashMap<>();collectionSupplier.put("data", () -> {if (hasNext.get()) {hasNext.set(false);return collection;}return null;});write(os, template, sheetNo, null, collectionSupplier, null);}/*** 写入Excel文件* @param os IO输出流* @param template 模板文件IO输入流* @param sheetName Sheet名称* @param collection 集合数据*/public static void writeCollection(OutputStream os, InputStream template, String sheetName, Collection<?> collection) {AtomicBoolean hasNext = new AtomicBoolean(true);Map<String, Supplier<Collection<?>>> collectionSupplier = new HashMap<>();collectionSupplier.put("data", () -> {if (hasNext.get()) {hasNext.set(false);return collection;}return null;});write(os, template, null, sheetName, collectionSupplier, null);}/*** 写入Excel文件* @param os IO输出流* @param template 模板文件IO输入流* @param sheetNo Sheet序号,从0开始* @param collectionSupplier 集合数据提供者* @param normalMap 普通数据*/public static void write(OutputStream os, InputStream template, Integer sheetNo, Map<String, Supplier<Collection<?>>> collectionSupplier, Map<String, Object> normalMap) {write(os, template, sheetNo, null, collectionSupplier, normalMap);}/*** 写入Excel文件* @param os IO输出流* @param template 模板文件IO输入流* @param sheetName Sheet名称* @param collectionSupplier 集合数据提供者* @param normalMap 普通数据*/public static void write(OutputStream os, InputStream template, String sheetName, Map<String, Supplier<Collection<?>>> collectionSupplier, Map<String, Object> normalMap) {write(os, template, null, sheetName, collectionSupplier, normalMap);}/*** 写入Excel文件* @param os IO输出流* @param template 模板文件IO输入流* @param sheetNo Sheet序号,从0开始* @param sheetName Sheet名称* @param collectionSupplier 集合数据提供者* @param normalMap 普通数据*/public static void write(OutputStream os, InputStream template, Integer sheetNo, String sheetName, Map<String, Supplier<Collection<?>>> collectionSupplier, Map<String, Object> normalMap) {// 校验数据if (normalMap != null) {normalMap.values().forEach(value -> {if (value instanceof Collection) {throw new ExcelAnalysisException("普通数据中不允许有集合类型");}});}// 构建SheetWriteSheet sheet;if (sheetNo == null) {if (sheetName == null) {sheet = EasyExcel.writerSheet().build();} else {sheet = EasyExcel.writerSheet(sheetName).build();}} else {if (sheetName == null) {sheet = EasyExcel.writerSheet(sheetNo).build();} else {sheet = EasyExcel.writerSheet(sheetNo, sheetName).build();}}// 写入数据try (ExcelWriter excelWriter = EasyExcel.write(os).withTemplate(template).build();) {// 填充集合数据if (collectionSupplier != null) {// 对集合数据遍历的必要配置FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();// 对集合进行一个一个排队渲染(若不一个一个来,会造成数据丢失)collectionSupplier.forEach((key, supplier) -> {while (true) {Collection<?> collection = supplier.get();if (collection == null || collection.isEmpty()) {break;}excelWriter.fill(new FillWrapper(key, collection), fillConfig, sheet);}});}// 填充普通数据if (normalMap != null) {excelWriter.fill(normalMap, sheet);}}}/*** 日期时间转换器*/public static class LocalDateTimeConverter implements Converter<LocalDateTime> {/** 日期时间格式对象:yyyy-MM-dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/** 日期时间格式对象:yyyy/MM/dd HH:mm:ss */private static final DateTimeFormatter DATETIME_FORMATTER_2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");/** 日期时间格式对象:yyyy年MM月dd日 HH时mm分ss秒 */private static final DateTimeFormatter DATETIME_FORMATTER_3 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");/*** Java对象类型*/@Overridepublic Class<?> supportJavaTypeKey() {return LocalDateTime.class;}/*** 单元格格式类型*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 单元格内容转成Java对象*/@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) throws Exception {String value = context.getReadCellData().getStringValue();LocalDateTime result;try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_1);} catch (DateTimeParseException e) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_2);} catch (DateTimeParseException e1) {try {result = LocalDateTime.parse(value, DATETIME_FORMATTER_3);} catch (DateTimeParseException e2) {throw new ExcelDataConvertException(context.getReadCellData().getRowIndex(), context.getReadCellData().getColumnIndex(), context.getReadCellData(), context.getContentProperty(), "无法解析日期时间字符串:" + value);}}}return result;}/*** Java对象转成单元格内容*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {return new WriteCellData<>(context.getValue());}}}