# EasyExcel学习笔记 **Repository Path**: a-bundle/easy-excel-exercise ## Basic Information - **Project Name**: EasyExcel学习笔记 - **Description**: 根据B站黑马程序员练习EasyExcel的相关代码与文档 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-10-03 - **Last Updated**: 2025-10-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # EasyExcel ## 一、初始EasyExcel ### 1、Apache POI 先说`POI`,有过报表导入导出经验的同学,应该听过或者使用。 `Apache POI`是Apache软件基金会的开源函式库,提供跨平台的`Java API`实现`Microsoft Office`格式档案读写。但是存在如下一些问题:` #### 1.1 学习使用成本较高 对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。 想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。 POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。 #### 1.2 POI的内存消耗较大 大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。 总体上来说,简单写法重度依赖内存,复杂写法学习成本高。 #### 特点 1. 功能强大 2. 代码书写冗余繁杂 3. 读写大文件耗费内存较大,容易OOM ### 2、EasyExcel #### 2.1 重写了POI对07版Excel的解析 - EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。 - 下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点) - 在上层做了模型转换的封装,让使用者更加简单方 - #### 特点 1. 在数据模型层面进行了封装,**使用简单** 2. 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM 3. 只能操作Excel 4. 不能读取图片 5. 使用程序读写excel,数据在excel文件、程序<实体类、Map>这两个载体中间相互流转。 ## 二、快速入门--QuickStart ### 1、导入依赖 ~~~xml com.alibaba easyexcel 2.1.6 org.projectlombok lombok 1.18.10 junit junit 4.12 ~~~ ### 2、最简单的读 #### 2.1 准备工作 需求:单实体导入。 导入Excel学员信息到系统。 包含如下列:姓名、性别、出生日期。 模板详见:excel.xls。 #### 2.1 数据实体类 ~~~java package model; import lombok.Data; import java.util.Date; /** * 学生实体类 */ @Data public class Student { private String name; // 学生姓名 private String gender; // 学生性别 private Date birthday; // 学生年龄 private String id; // 学生ID } ~~~ #### 2.3 读取Excel文件 调用`EasyExcel`的`API`读取的`Excel`文件的测试类`ExcelTest` ~~~java package com.xydream.easyexcel; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.read.builder.ExcelReaderBuilder; import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder; import easyexcel.EasyExcelListener; import model.Student; import org.junit.Test; public class ExcelTest { @Test public void test01() { /** * 构建一个读的工作牌对象 * * @param pathName 要读的文件的路径 * @param head 文件中每一行数据要储存的实体的类型的class * @param readListener 读监听器,每读一行内容,都会调用一次该对象的invoke,在invoke可以操作使用读取到的数据 * @return Excel reader builder */ // 获得一个工作簿对象 ExcelReaderBuilder readWorkBook = EasyExcel.read("easyExcel.xlsx", Student.class, new EasyExcelListener()); // 获得一个工作表对象 ExcelReaderSheetBuilder sheet = readWorkBook.sheet(); // 读取工作表中的内容 sheet.doRead(); } } ~~~ 读取Excel的监听器,用于处理生产的数据 ~~~java package easyexcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import model.Student; public class EasyExcelListener extends AnalysisEventListener { // 每读一样,会调用该invoke方法一次 public void invoke(Student student, AnalysisContext analysisContext) { System.out.println("student" + student); } // 全部读完之后,会调用该方法 public void doAfterAllAnalysed(AnalysisContext analysisContext) { } } ~~~ ### 3、最简单的写 #### 3.1 准备工作 需求:单实体到处 导出多个学生对象到Excel表格 包含如下列:姓名、性别、出生日期 模板详细:easyExcel-write.xlsx #### 3.2 数据实体类 ~~~java package model; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; import lombok.Data; import java.util.Date; /** * 学生实体类 * @ContentRowHeight 设置内容的行高 * @HeadRowHeight 设置表头的行高 */ @Data public class Student { /** * @ExcelProperty 设置列表的列头 * value 对应Excel表中的列头 * index 对应Excel表中的列数,默认-1,建议指定时从0开始 * @ColumnWidth 设置列的列宽 */ @ExcelProperty(value = "学生姓名", index = 1) @ColumnWidth(20) private String name; // 学生姓名 @ExcelProperty(value = "学生性别", index = 3) @ColumnWidth(15) private String gender; // 学生性别 @ExcelProperty(value = "年龄", index = 2) @ColumnWidth(20) private Date birthday; // 学生年龄 @ExcelProperty(value = "ID", index = 0) private String id; // 学生ID } ~~~ #### 3.3 写入Excel文件 ~~~java package com.xydream.easyexcel; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.read.builder.ExcelReaderBuilder; import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder; import com.alibaba.excel.write.builder.ExcelWriterBuilder; import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; import easyexcel.EasyExcelListener; import model.Student; import org.junit.Test; import java.util.ArrayList; import java.util.Date; import java.util.List; public class ExcelTest { /** * 写入Excel */ @Test public void test() { /** * 构建一个读的工作牌对象 * * @param pathName 要读的文件的路径 * @param head 文件中每一行数据要储存的实体的类型的class * @param readListener 读监听器,每读一行内容,都会调用一次该对象的invoke,在invoke可以操作使用读取到的数据 * @return Excel reader builder */ // 获得一个工作簿对象 ExcelWriterBuilder writeWorkBook = EasyExcel.write("easyExcel-write.xlsx", Student.class); // 获得一个工作表对象 ExcelWriterSheetBuilder sheet = writeWorkBook.sheet(); // 准备数据 List students = initDate(); // 读取工作表中的内容 sheet.doWrite(students); } /** * 初始化数据 * @return */ private static List initDate() { ArrayList students = new ArrayList(); Student data = new Student(); for (int i = 0; i < 10; i++) { data.setName("学号000" + 1); data.setGender("男"); data.setBirthday(new Date());; students.add(data); } return students; } } ~~~ ### 4、API常用注解 #### 4.1 @ExcelProperty注解 标注在成员变量上,加上该注解可以让该成员变量参与读写,同时可以设置该字段的列头属性。`value` 对应Excel表中的列头,如果不指定则使用成员变量的名字作为列头。`index`对应Excel表中的列数,默认-1,建议指定时从0开始,如过不指定则根据成员变量位置排序。 #### 4.2 @ColumnWidth注解 标注在成员变量上,加上该注解可以设置该字段的列宽 #### 4.3 @ExcelIgnore注解 标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段 #### 4.4 @DateTimeFormat注解 标注在成员变量上,容器转换,代码中String类型的成员变量去接收`excel中日期格式的数据`会调用这个注解。里面的`value`参照`java.test.SimpleDateFormat` #### 4.5 @NumberFormat注解 标注在成员变量上,数字转换,代码中用String类型的成员变量去接收`excel数字格式的数据`会调用这个注解。里面的`value`参照`java.text.DecimalFormat` #### 4.6 @ContentRowHeight注解 标注在类上,主要设置Excel中内容的行高 #### 4.7 @HeadRowHeight注解 标注在类上,主要设置Excel中表头的行高 #### 4.8 @ExcelIgnoreUnannotated注解 标注在类上,不标注该注解时,默认类中的所有成员变量都会参与读写,无论是否在成员变量上加了`@ExcelProperty`注解 ### 5、读取时通用参数 `ReadWorkBook`,`ReadSheet`都会有的参数,如果为空,默认使用上级。 * `converter`转换器,默认加载了很多转换器,也可以自定义。 * `readListener` 监听器,在读取数据的过程中会不断的调用监听器。 * `headRowNumber` 指定需要读表格列头行数。默认有一行头,也就是认为第二行开始起为数据。 * `head`与`clazz`二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用class,就是文件中每一行数据对应的代码中的实体类型。 * `head`与`clazz`二选一。读取文件头对应的class,也可以使用注解。如歌两个都不指定,则会读取全部数据。 * `autoTrim`字符串,表头等数据自动trim(去空格)。 * `password`读的审核是否需要使用密码。 ### 6、ReadWorkBook(工作簿对象)参数 * `excelType`当前excel的类型,读取时会自动判断,无需设置。 * `inputStream`与`file`二选一。建议使用file。 * `file`与`inputStream`二选一。读取文件的文件。 * `autoCloseStream`自动关闭流。 * `readCache`默认小鱼5M用内存,超过5M会使用`EhCache`,不介意使用这个参数。 ### 7、ReadSheet(工作表对象)参数 * `sheetNo`需要读取Sheet的编号,建议使用这个来指定读取哪个Sheet。 * `sheetName`根据名字去匹配Sheet,excel 2003不支持根据名字去匹配。 ## 三、实战案例 ### 1、填充简单模板并导出 1、在pom.xml中导入maven依赖 ~~~ xml com.alibaba easyexcel 3.1.1 ~~~ 2、在Resouces目录下添加模板文件 模板目录: ![image-20220830163424490](C:\Users\1\Pictures\Camera Roll\easyExcel\填充简单模板并导出1.png) 模板内容: ![image-20220830163608873](C:\Users\1\Pictures\Camera Roll\easyExcel\填充简单模板并导出2.png) 3、Controller层代码 ~~~ java /** * 最简单的模板填充并导出 * @param response * @param user */ @RequestMapping("downExcel") public void downExcel(HttpServletResponse response, User user) throws Exception { // 获取数据 List list = userService.getUserList(); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // 获取模板地址 InputStream templateFileName = Thread.currentThread().getContextClassLoader().getResourceAsStream("files/模板.xlsx"); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(templateFileName).build(); // 创建一个sheet页 WriteSheet writeSheet = EasyExcel.writerSheet(0).build(); // 加入数据 excelWriter.fill(list,writeSheet); excelWriter.finish(); } ~~~ 4、User实体类 ~~~ java import lombok.Data; @Data public class User { private String id; private String username; private String address; } ~~~ 5、Service层【这里直接省略了Service接口】 ~~~ java @Service public class UserService { public List getUserList() { return userDao.getUserList(); } } ~~~ 6、dao接口【通过mybatis注解直接查询数据库, 该方法需要在启动类中添加`@MapperScan`注解扫描UserDao】 ~~~ java public interface UserDao { @Select("select * from tb_user") List getUserList(); } ~~~ 7、启动类 ~~~java @MapperScan("com.xydream.user.dao") @SpringBootApplication public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } } ~~~ ### 2、填充复杂模板并导出 1、在pom.xml中导入maven依赖 ~~~ xml com.alibaba easyexcel 3.1.1 ~~~ 2、在Resouces目录下添加模板文件 模板目录: ![image-20220830163424490](C:\Users\1\Pictures\Camera Roll\easyExcel\填充简单模板并导出1.png) 模板内容: ![填充复杂模板并导出1](C:\Users\1\Pictures\Camera Roll\easyExcel\填充复杂模板并导出1.png) 3、Controller层代码 ~~~ java /** * 填充复杂模板并导出 * @param response * @param user */ @RequestMapping("downExcel") public void downExcel(HttpServletResponse response, User user) throws Exception { // 获取列表数据 List list = userService.getUserList(); // 获取用户列表统计数据 HashMap map = userService.getUserCount(list); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // 获取模板地址 InputStream templateFileName = Thread.currentThread().getContextClassLoader().getResourceAsStream("files/模板.xlsx"); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(templateFileName).build(); // 创建一个sheet页 WriteSheet writeSheet = EasyExcel.writerSheet(0).build(); // 加入用户列表数据 excelWriter.fill(list, writeSheet); // 加入用户列表统计数据 excelWriter.fill(map, writeSheet); excelWriter.finish(); } ~~~ 4、User实体类 ~~~ java import lombok.Data; @Data public class User { private String id; private String username; private String address; } ~~~ 5、Service层【这里直接省略了Service接口】 ~~~ java @Service public class UserService { public List getUserList() { return userDao.getUserList(); } public HashMap getUserCount(List list) { HashMap map = new HashMap<>(); if (list != null && list.size() > 0) { map.put("userCount", list.size()); } else { map.put("userCount", 0); } SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String date = simpleDateFormat.format(new Date()); map.put("date", date); return map; } } ~~~ 6、dao接口【通过mybatis注解直接查询数据库, 该方法需要在启动类中添加`@MapperScan`注解扫描UserDao】 ~~~ java public interface UserDao { @Select("select * from tb_user") List getUserList(); } ~~~ 7、启动类 ~~~java @MapperScan("com.xydream.user.dao") @SpringBootApplication public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } } ~~~ ###