夜雨聆风学习资料网

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 模式,在上层做了模型转换的封装,让使用者更加简单方便

官网
https://easyexcel.opensource.alibaba.com/

坐标
<dependency>  <groupId>com.alibaba</groupId>  <artifactId>easyexcel</artifactId>  <!-- 20240911号最后一次版本号,已经不再维护了 -->  <version>4.0.3</version></dependency>

读取示例

读取指定序号的 Sheet 内容
实体类
/** * 用户信息 */@Getter@Setter@ToStringpublic class UserInfo {  @ExcelProperty("姓名")  private String name;  @ExcelProperty("年龄")  private Integer age;  @ExcelProperty("生日")  private LocalDateTime birthday;  @ExcelProperty("地址")  private String address;}
Excel 文件内容
测试类
/** * 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>() {      @Override      publicvoidinvoke(UserInfo data, AnalysisContext context) {        System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);      }      @Override      publicvoiddoAfterAllAnalysed(AnalysisContext context) {        System.out.println("文件读取完毕");      }    });    // 读取第一个Sheet页    builder.sheet(0).doRead();  }}
日志打印结果

读取指定名称的 Sheet 内容
实体类
/** * 用户信息 */@Getter@Setter@ToStringpublic class UserInfo {  @ExcelProperty("姓名")  private String name;  @ExcelProperty("年龄")  private Integer age;  @ExcelProperty("生日")  private LocalDateTime birthday;  @ExcelProperty("地址")  private String address;}
Excel 文件内容
测试类
/** * 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>() {      @Override      publicvoidinvoke(UserInfo data, AnalysisContext context) {        System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);      }      @Override      publicvoiddoAfterAllAnalysed(AnalysisContext context) {        System.out.println("文件读取完毕");      }    });    // 读取指定名称的Sheet页    builder.sheet("班级信息").doRead();  }}
日志打印结果

表头不在首行的读取 Sheet 内容
实体类
/** * 用户信息 */@Getter@Setter@ToStringpublic class UserInfo {  @ExcelProperty("姓名")  private String name;  @ExcelProperty("年龄")  private Integer age;  @ExcelProperty("生日")  private LocalDateTime birthday;  @ExcelProperty("地址")  private String address;}
Excel 文件内容
测试类
/** * 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>() {      @Override      publicvoidinvoke(UserInfo data, AnalysisContext context) {        System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);      }      @Override      publicvoiddoAfterAllAnalysed(AnalysisContext context) {        System.out.println("文件读取完毕");      }    });    // 读取第一个Sheet页、并且标识第4行为表头    builder.sheet(0).headRowNumber(4).doRead();  }}
日志打印结果

读取整个 Excel 文件内容
实体类
/** * 用户信息 */@Getter@Setter@ToStringpublic class UserInfo {  @ExcelProperty("姓名")  private String name;  @ExcelProperty("年龄")  private Integer age;  @ExcelProperty("生日")  private LocalDateTime birthday;  @ExcelProperty("地址")  private String address;}
Excel 文件内容
测试类
/** * 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>() {      @Override      publicvoidinvoke(UserInfo data, AnalysisContext context) {        System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);      }      @Override      publicvoiddoAfterAllAnalysed(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对象类型   */  @Override  public Class<?> supportJavaTypeKey() {    return LocalDateTime.class;  }  /**   * 单元格格式类型   */  @Override  public CellDataTypeEnum supportExcelTypeKey() {    return CellDataTypeEnum.STRING;  }  /**   * 单元格内容转成Java对象   */  @Override  public 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对象转成单元格内容   */  @Override  public 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 文件内容
测试类
/** * 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>() {      @Override      publicvoidinvoke(UserInfo data, AnalysisContext context) {        System.out.printf("第%d行:%s%n", context.readRowHolder().getRowIndex(), data);      }      @Override      publicvoiddoAfterAllAnalysed(AnalysisContext context) {        System.out.println("文件读取完毕");      }    });    // 读取第一个Sheet    builder.sheet(0).doRead();  }}
日志打印结果

写入示例

注意
  • POJO 数据、Map 数据不要内嵌 POJO、Map,EasyExcel 无法实现多层次获取数据并填充,需要通过打平方式来解决该问题
  • 填充集合数据时,必须使用 FillConfig.builder().forceNewRow(Boolean.TRUE).build() 该配置
  • 填充复杂数据时,必须先填充集合数据、再填充其他数据,否则可能会出现数据丢失、排版错误等问题

将 POJO 数据写入指定序号的 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对象类型   */  @Override  public Class<?> supportJavaTypeKey() {    return LocalDateTime.class;  }  /**   * 单元格格式类型   */  @Override  public CellDataTypeEnum supportExcelTypeKey() {    return CellDataTypeEnum.STRING;  }  /**   * 单元格内容转成Java对象   */  @Override  public 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对象转成单元格内容   */  @Override  public 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 模板文件
测试类
/** * Excel测试类 */public class ExcelTest {  publicstaticvoidmain(String[] args) {    // 创建POJO    UserInfo 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);  }}
Excel 目标文件

将 POJO 数据写入指定名称的 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对象类型   */  @Override  public Class<?> supportJavaTypeKey() {    return LocalDateTime.class;  }  /**   * 单元格格式类型   */  @Override  public CellDataTypeEnum supportExcelTypeKey() {    return CellDataTypeEnum.STRING;  }  /**   * 单元格内容转成Java对象   */  @Override  public 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对象转成单元格内容   */  @Override  public 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 模板文件
测试类
/** * Excel测试类 */public class ExcelTest {  publicstaticvoidmain(String[] args) {    // 创建POJO    UserInfo 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 目标文件

将 Map 数据写入指定序号的 Sheet
Excel 模板文件
测试类
/** * Excel测试类 */public class ExcelTest {  public static void main(String[] args) {    // 创建Map    Map<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 目标文件

将 Map 数据写入指定名称的 Sheet
Excel 模板文件
测试类
/** * Excel测试类 */public class ExcelTest {  public static void main(String[] args) {    // 创建Map    Map<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);  }}
Excel 目标文件

将集合数据写入指定序号的 Sheet
Excel 模板文件
日期时间转换器
/** * 日期时间转换器 */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对象类型   */  @Override  public Class<?> supportJavaTypeKey() {    return LocalDateTime.class;  }  /**   * 单元格格式类型   */  @Override  public CellDataTypeEnum supportExcelTypeKey() {    return CellDataTypeEnum.STRING;  }  /**   * 单元格内容转成Java对象   */  @Override  public 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对象转成单元格内容   */  @Override  public 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());  }}
Excel 目标文件

将集合数据写入指定名称的 Sheet
Excel 模板文件
日期时间转换器
/** * 日期时间转换器 */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对象类型   */  @Override  public Class<?> supportJavaTypeKey() {    return LocalDateTime.class;  }  /**   * 单元格格式类型   */  @Override  public CellDataTypeEnum supportExcelTypeKey() {    return CellDataTypeEnum.STRING;  }  /**   * 单元格内容转成Java对象   */  @Override  public 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对象转成单元格内容   */  @Override  public 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());  }}
Excel 目标文件

将 Map 数据、集合数据一起写入指定序号的 Sheet
Excel 模板文件
日期时间转换器
/** * 日期时间转换器 */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对象类型   */  @Override  public Class<?> supportJavaTypeKey() {    return LocalDateTime.class;  }  /**   * 单元格格式类型   */  @Override  public CellDataTypeEnum supportExcelTypeKey() {    return CellDataTypeEnum.STRING;  }  /**   * 单元格内容转成Java对象   */  @Override  public 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对象转成单元格内容   */  @Override  public 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);    // 创建Map    Map<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);    }  }}
Excel 目标文件

将 Map 数据、集合数据一起写入指定名称的 Sheet
Excel 模板文件
日期时间转换器
/** * 日期时间转换器 */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对象类型   */  @Override  public Class<?> supportJavaTypeKey() {    return LocalDateTime.class;  }  /**   * 单元格格式类型   */  @Override  public CellDataTypeEnum supportExcelTypeKey() {    return CellDataTypeEnum.STRING;  }  /**   * 单元格内容转成Java对象   */  @Override  public 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对象转成单元格内容   */  @Override  public 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);    // 创建Map    Map<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);    }  }}
Excel 目标文件

通用基础工具类
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>() {      @Override      public void invoke(T data, AnalysisContext context) {        consumer.accept(context.readRowHolder().getRowIndex(), data);      }      @Override      public 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("普通数据中不允许有集合类型");        }      });    }    // 构建Sheet    WriteSheet 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对象类型     */    @Override    public Class<?> supportJavaTypeKey() {      return LocalDateTime.class;    }    /**     * 单元格格式类型     */    @Override    public CellDataTypeEnum supportExcelTypeKey() {      return CellDataTypeEnum.STRING;    }    /**     * 单元格内容转成Java对象     */    @Override    public 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对象转成单元格内容     */    @Override    public WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) throws Exception {      return new WriteCellData<>(context.getValue());    }  }}

相关学习资料