当前位置: 首页 > news >正文

EasyExcel详解

文章目录

    • 一、easyExcel
      • 1.什么是easyExcel
      • 2.easyExcel示例demo
      • 3.easyExcel read的底层逻辑
      • ~~4.easyExcel write的底层逻辑~~
    • 二、FastExcel
      • 1.为什么更换为fastExcel
      • 2.fastExcel新功能

一、easyExcel

1.什么是easyExcel

内容摘自官方:Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

通俗解释就是说:一个基于poi的excel简化开发包,性能比poi要好,且易于使用

官方文档地址
源码地址

2.easyExcel示例demo

官方文档非常全面,本无需写一个demo来记录。本demo旨在展示easyExcel的读写基础用法、自定义类型转换、自定义单元格格式及excel空白行处理等,可以理解为将常用的情况记录下来,省去看官方文档的时间。

## PersonVO.class,代码中的Person.classPersonVO.class的区别为没有ifOffer字段,为了展示而做了区分
## Person.class是用来读excel的,PersonVO.class用来写excel
@Data
public class PersonVo {@ExcelProperty("名称")private String name;@ExcelProperty("性别")private String gender;@ExcelProperty("年龄")private Integer age;@ExcelProperty("信息")private String info;@ExcelProperty("评分")private Float score;// OfferEnumConverter为自定义的Converter,用来做OfferEnum和String的映射@ExcelProperty(value = "是否录用", converter = OfferEnumConverter.class)private OfferEnum ifOffer;
}## excel读及写部分,如果read时使用PersonVo.class映射表头
## 则可以在CustomPageReadListener.class的invoke方法中,做对person.ifOffer的赋值File file = new File("D:\\develop\\work\\test.xlsx");
try (InputStream is = Files.newInputStream(file.toPath())) {// 读取数据List<PersonVo> excelDatas = new ArrayList<>();EasyExcel.read(is, Person.class, new CustomPageReadListener<Person>(dataList -> {if (CollectionUtils.isEmpty(dataList)) {return;}dataList.forEach(data -> {PersonVo personVo = new PersonVo();BeanUtils.copyProperties(data, personVo);excelDatas.add(personVo);});})).sheet().doReadSync();// 为了实现自定义表格样式,根据ifOffer来决定行颜色Map<Integer, Short> cellColorType = new HashMap<>();for (int i = 0; i < excelDatas.size(); i++) {PersonVo person = excelDatas.get(i);if (person.getScore() > 3) {person.setIfOffer(OfferEnum.OFFER);cellColorType.put(i + 1, IndexedColors.GREEN.getIndex());} else if (person.getScore() < 2) {person.setIfOffer(OfferEnum.REFUSE);cellColorType.put(i + 1, IndexedColors.RED.getIndex());} else {person.setIfOffer(OfferEnum.WAIT);cellColorType.put(i + 1, IndexedColors.YELLOW.getIndex());}}EasyExcel.write("D:\\develop\\work\\test1.xlsx", PersonVo.class).registerWriteHandler(new CustomCellWriteHandler(cellColorType)).sheet("测试").doWrite(excelDatas);
} catch (IOException e) {throw new RuntimeException(e);
}

demo中用到了自定义类型转换OfferEnumConverter、自定义excel读取监听器CustomPageReadListener、自定义WriteHandler CustomCellWriteHandler,是实际开发中这三个是最常用的工具

  1. OfferEnumConverter: String <–> Enum转换器,实现supportJavaTypeKey及supportExcelTypeKey是为了在Easy.registerConverter()注册通用转换器也可以使用
## OfferEnumConverter.class
public class OfferEnumConverter implements Converter<OfferEnum> {@Overridepublic Class<OfferEnum> supportJavaTypeKey() {return OfferEnum.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}@Overridepublic OfferEnum convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {return OfferEnum.valueOf(cellData.getStringValue());}@Overridepublic WriteCellData<?> convertToExcelData(OfferEnum value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {if(Objects.isNull(value)) {return new WriteCellData<>("");} else {return new WriteCellData<>(value.getValue());}}
}## OfferEnum.class 录用标记枚举
@Getter
public enum OfferEnum {OFFER("y", "录用"),REFUSE("n", "不录用"),WAIT("wait", "待定");;private final String value;private final String desc;OfferEnum(String value, String desc) {this.value = value;this.desc = desc;}public static OfferEnum getByValue(String value) {for (OfferEnum offerEnum : OfferEnum.values()) {if (offerEnum.value.equals(value)) {return offerEnum;}}return WAIT;}
}
  1. CustomPageReadListener: 监听器是在读取完一行数据后被调用的,invoke中接收到的是一行的数据。这里做了处理空行的操作,虽然EasyExcel默认情况下会配置ignoreEmptyRow为true,但是如果行内某个单元格无数据但有单元格式,会被EasyExcel认为非空行,因此对空行严谨的项目需要在这里处理一下空行。
public class CustomPageReadListener<T> extends PageReadListener<T> {public CustomPageReadListener(Consumer<List<T>> consumer) {super(consumer);}@Overridepublic void invoke(T data, AnalysisContext context) {// 处理空行if (isNullLine(data)) {return;}// 特殊字段赋值及处理(如:dateStr赋值给date)flushData(data);// 处理数据转换异常super.invoke(data, context);}private void flushData(T data) {}private boolean isNullLine(T data) {System.err.println(JSON.toJSONString(data));// 获取data每个字段,反射判断是不是都为空或空字符串for (Field field : data.getClass().getDeclaredFields()) {field.setAccessible(true);try {Object value = field.get(data);if (value instanceof String) {if (!StringUtils.isEmpty(value)) {return false;}} else {if (Objects.nonNull(value)) {return false;}}} catch (IllegalAccessException e) {return false;}}return true;}
}
  1. CustomCellWriteHandler: 将内存中的数据写入excel时,需要做一些特殊处理时(如:脱敏处理、添加单元格样式、合并单元格等),可以通过实现WriteHandler来实现功能,demo中只有添加单元格样式,官方文档中有很全面的各种案例用法
public class CustomCellWriteHandler implements CellWriteHandler {private final Map<Integer, Short> cellColorType;public CustomCellWriteHandler(Map<Integer, Short> cellColorType) {if(Objects.isNull(cellColorType)) {cellColorType = new HashMap<>();}this.cellColorType = cellColorType;}@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 表头样式不变if (BooleanUtils.isNotTrue(context.getHead())) {int rowIndex = context.getRowIndex();Short colorIndex = cellColorType.get(rowIndex);if(Objects.nonNull(colorIndex)) {WriteCellData<?> cellData = context.getFirstCellData();// 这里需要去cellData 获取样式// 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat// ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了// 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();writeCellStyle.setFillForegroundColor(colorIndex);// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDwriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);}}}
}

本案例使用的test.excel数据及导出后的效果参照下图:
Excel测试数据
WriteExcel结果

3.easyExcel read的底层逻辑

通过ExcelAnalyser来配置excel解析执行器

  • 通过FileMagic来读取文件开头几个字节的魔数,以确定文件的类型。为了兼容CSV文件,通过File方式readExcel的时候,通过判断文件的后缀名称是否为.csv来判断是否为CSV文件
  • 设置read上下文:解析表头,加载readListener、Converter(预定义的Converter和通过registerConverter注册的Converter)、设置忽略空行(如果空行中有表格样式,则无法忽略)及readCache
  • 设置read执行器:选择合适的执行器,并加载所有的sheet。这里加载了所有的sheet,在read的时候会根据条件选择要读取的sheet

通过ExcelAnalyser.analysis来解析excel

  • 从xlsx视角出发的,xls和csv这里不做展示
  • XlsxSaxAnalyser.parseXmlSource()中使用SAXParserFactory来解析 Excel 文件底层 XML 结构。SAXParserFactory基于 SAX(Simple API for XML)事件驱动模型实现高效的大文件流式解析,避免内存溢出(OOM)
  • XlsxRowHandler重写了startElement来实现对每一行每一个单元格的读取。当所有XlsxTagHandler执行完后,开始endElement进行cell类型的转换等,最终交给AnalysisEventProcessor.endRow来处理数据,并调用ReadListener监听器来对数据做处理(如PageReadListener来缓存数据)
  • EasyExcel有四个解析excel的入口,分别为
    • .sheet().doRead() – sheet中不加参数,则默认取sheetNo为0的sheet,doRead中进行解析excel
    • .sheet().doReadSync() – 相对doRead(),注册了一个新的Listener用来缓存数据,读取excel结束后直接从Listner中读取数据并return
    • doReadAll() – 顾名思义,读取所有的sheet(),并映射到同一个实体list中,适合同类型分页数据
    • .doReadAllSync() – 同上
  • 读取excel的关键为SAXParserFactory和ReadCache,具体逻辑可以自己阅读源码,或使用AI工具辅助阅读

4.easyExcel write的底层逻辑


二、FastExcel

文本采用的fastExcel版本为1.0.0,当前时间最新版本为1.2.0

目前FastExcel官网已挂,仅有开源源码地址

1.为什么更换为fastExcel

  • 2024年8月阿里已宣布停止更新easyExcel,同时原作者宣布新开发fastExcel,支持所有easyExcel的功能,因此原easyExcel用户可以最低成本过度到fastExcel
  • fastExcel通过对底层算法的优化和内存管理的改进,能更高效的处理大规模的excel数据,大幅降低内存消耗和处理时间
  • 新功能:读取excel指定行数,excel转pdf(注意:仅仅是将excel文件转为pdf文件,且在1.1.0版本中已经移除此功能,谨慎使用

2.fastExcel新功能

## fastExcel中既可以用FastExcel.class,也可以用EasyExcel.class,除了1.0.0版本外,俩完全一样
## .numRows()即读取excel指定行数,.numRows(10)即从表头开始读10,上文中的案例,就只会读到9条数据
FastExcel.read(is, Person.class, new PageReadListener<Person>(dataList -> {if (CollectionUtils.isEmpty(dataList)) {return;}dataList.forEach(data -> {PersonVo personVo = new PersonVo();BeanUtils.copyProperties(data, personVo);excelDatas.add(personVo);});
})).sheet().numRows(10).doRead();## excel文件转为pdf文件,谨慎使用
FastExcel.convertToPdf(new File("D:\\develop\\work\\test1.xlsx"), new File("D:\\develop\\work\\test2.pdf"), null, null);

相关文章:

  • 2025年,多模态特征融合只会更火
  • 争对机器学习和深度学习里Python项目开发管理项目依赖的工具中方便第三方库和包的安装
  • 【MyBatis插件】PageHelper 分页
  • 飞牛NAS本地部署开源TTS文本转语音工具EasyVoice与远程使用流程
  • 前端流行框架Vue3教程:17. _组件数据传递
  • 深入解析HTTP协议演进:从1.0到3.0的全面对比
  • 2025认证杯数学建模第二阶段A题小行星轨迹预测思路+模型+代码
  • 机器学习中采样哪些事
  • React 第四十二节 Router 中useLoaderData的用途详解
  • 牛客网NC22015:最大值和最小值
  • 全面解析机器学习与深度学习中的模型权重文件格式与应用场景
  • 【HarmonyOS 5】鸿蒙mPaaS详解
  • 《Python星球日记》 第80天:目标检测(YOLO、Mask R-CNN)
  • Uniapp 安卓实现讯飞语音听写(复制即用)
  • 隆重推荐(Android 和 iOS)UI 自动化工具—Maestro
  • [数据结构]7. 堆-Heap
  • 单片机-STM32部分:17、数码管
  • Elasticsearch 分片机制高频面试题(含参考答案)
  • 乡村农家游乐小程序源码介绍
  • 【测试工具】selenium和playwright如何选择去构建自动化平台
  • 中办、国办关于持续推进城市更新行动的意见
  • 外交部:国际社会广泛理解和支持中方不同意台参加世卫大会的决定
  • 工商银行杭州金融研修院原院长蒋伟被“双开”
  • 广东省原省长卢瑞华逝世,享年88岁
  • 教育部:启动实施县中头雁教师岗位计划,支撑县中全面振兴
  • 超新星|罚丢点球的那道坎,刘诚宇靠自己迈了过去