EasyExcel
- EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。
依赖:
<!-- EasyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
常用注解和API
常用类
EasyExcel 入口类,用于构建开始各种操作
ExcelReaderBuilder ExcelWriterBuilder 构建出一个 ReadWorkbook WriteWorkbook,可以理解成一个excel对象,一个excel只要构建一个
ExcelReaderSheetBuilder ExcelWriterSheetBuilder 构建出一个 ReadSheet WriteSheet对象,可以理解成excel里面的一页,每一页都要构建一个
ReadListener 在每一行读取完毕后都会调用ReadListener来处理数据
WriteHandler 在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据
所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel…sheet()方法之前作用域是整个sheet,之后针对单个sheet
读取时的注解
@ExcelProperty
使用位置:标准作用在成员变量上
可选属性:
| 属性名 | 含义 | 说明 |
|---|---|---|
| index | 对应Excel表中的列数 | 默认-1,建议指定时从0开始 |
| value | 对应Excel表中的列头 | |
| converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。
如果不使用@ExcelProperty注解,成员变量从上到下的顺序,对应表格中从左到右的顺序;
使用建议:要么全部不写,要么全部用index,要么全部用名字去匹配,尽量不要三个混着用。
// 1. 修改成员变量顺序读取Excel表格
// 2. 修改index属性值读取Excel表格
// 3. 修改value属性值读取Excel表格
@ExcelIgnore
标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
代码演示:
// 4. 忽略id成员变量值读取Excel表格
@DateTimeFormat
标注在成员变量上,日期转换,代码中用String类型的成员变量去接收excel中日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
// 5. 按照指定的格式写入Excel内容
@NumberFormat
标注在成员变量上,数字转换,代码中用String类型的成员变量去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
@ExcelIgnoreUnannotated
标注在类上。
不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty 的注解。
标注该注解后,类中的成员变量如果没有标注@ExcelProperty 注解将不会参与读写。
读取时通用参数
ReadWorkbook(工作簿ExcelReaderBuilder),ReadSheet(工作表ExcelReaderSheetBuilder) 都会有的参数👀👀,如果为空,默认使用上级。
converter转换器,默认加载了很多转换器。也可以自定义。readListener监听器,在读取数据的过程中会不断的调用监听器。headRowNumber指定需要读表格的 列头行数。默认有一行头,也就是认为第二行开始起为数据。head与clazz二选一。读取文件头对应的列表,会根据列表匹配数据。建议使用clas,就是文件中每一行数据对应的代码中的实体类型。clazz与head二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。autoTrim字符串、表头等数据自动trimpassword读的时候是否需要使用密码
ReadWorkbook(工作簿对象)参数
excelType当前excel的类型,读取时会自动判断,无需设置。inputStream与file二选一。建议使用file。file与inputStream二选一。读取文件的文件。autoCloseStream自动关闭流。readCache默认小于5M用 内存,超过5M会使用EhCache,不建议使用这个参数。useDefaultListener@since 2.1.4默认会加入ModelBuildEventListener来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到class对象,请调用readListener方法,加入自定义的beforeListener、ModelBuildEventListener、 自定义的afterListener即可。
ReadSheet(工作表对象)参数
sheetNo需要读取Sheet的编号,建议使用这个来指定读取哪个SheetsheetName根据名字去匹配Sheet,excel 2003不支持根据名字去匹配
写入时的注解
@ExcelProperty
使用位置:标准作用在成员变量上
可选属性:
| 属性名 | 含义 | 说明 |
|---|---|---|
| index | 对应Excel表中的列数 | 默认-1,指定时建议从0开始 |
| value | 对应Excel表中的列头 | |
| converter | 成员变量转换器 | 自定义转换器需要实Converter接口 |
使用效果:index 指定写到第几列,如果不指定则根据成员变量位置排序;
value指定写入的列头,如果不指定则使用成员变量的名字作为列头;
如果要设置复杂的头,可以为value指定多个值。
其他注解:
基本和读取时一致
- @ContentRowHeight() 标注在类上或属性上,指定内容行高
- @HeadRowHeight() 标注在类上或属性上,指定列头行高
- @ColumnWidth() 标注在类上或属性上,指定列宽
- @ExcelIgnore 默认所有字段都会写入excel,这个注解会忽略这个字段
- @DateTimeFormat
日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat - @NumberFormat
数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat` - @ExcelIgnoreUnannotated
默认不加ExcelProperty` 的注解的都会参与读写,加了不会参与
写入时通用参数
ReadWorkbook(工作簿ExcelReaderBuilder),ReadSheet(工作表ExcelReaderSheetBuilder) 都会有的参数👀👀,如果为空,默认使用上级。
converter转换器,默认加载了很多转换器。也可以自定义。writeHandler写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用,对使用者透明不可见。relativeHeadRowIndex距离多少行后开始。也就是开头空几行needHead是否导出头head与clazz二选一。写入文件的头列表,建议使用class。clazz与head二选一。写入文件的头对应的class,也可以使用注解。autoTrim字符串、表头等数据自动trim
WriteWorkbook(工作簿对象)参数
excelType当前excel的类型,默认为xlsxoutputStream与file二选一。写入文件的流file与outputStream二选一。写入的文件templateInputStream模板的文件流templateFile模板文件autoCloseStream自动关闭流。password写的时候是否需要使用密码useDefaultStyle写的时候是否是使用默认头
WriteSheet(工作表对象)参数
sheetNo需要写入的编号。默认0sheetName需要些的Sheet名称,默认同sheetNo
读取Excel文件
实体-注意:实体的属性要与表格中的数据位置对应一直
/**
* Description:
* Created by 大师兄 on 2021/11/20 16:39
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
/**
* 学生姓名
*/
private String name;
/**
* 学生性别
*/
private String gender;
/**
* 学生出生日期
*/
private String birthday;
/**
* id
*/
private String id;
}
调用EasyExcel的API读取的Excel文件的测试类StudentReadDemo
/**
* Description:
* Created by 大师兄 on 2021/11/20 17:01
*/
public class StudentReadDemo {
public static void main(String[] args) throws FileNotFoundException {
// 读取文件,读取完之后会自动关闭
/*
pathName 文件路径;"d:\\test.xls"
head 每行数据对应的实体;Student.class
readListener 读监听器(继承AnalysisEventListener),每读一样就会调用一次该监听器的invoke方法
sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字,不传默认为0
*/
// 封装工作簿对象
ExcelReaderBuilder workBook = EasyExcel.read
("d:\\test.xlsx", Student.class, new StudentReadListener());
//workBook.headRowNumber(3); 从索引为3的位置开始读
// 封装工作表
ExcelReaderSheetBuilder sheet1 = workBook.sheet();//默认获取第一个工作表
// 读取
sheet1.doRead();
}
}
test.xls:

StudentReadListener:
/**
* Description:
* Created by 大师兄 on 2021/11/20 16:53
*/
public class StudentReadListener extends AnalysisEventListener<Student> {
// 每读一行,会调用该invoke方法一次,在invoke可以操作读取到的数据
public void invoke(Student data, AnalysisContext context) {
System.out.println("data = " + data);
}
// 全部读完之后,会调用该方法
public void doAfterAllAnalysed(AnalysisContext context) {
// TODO......
}
}
text.xlsx数据:
姓名 性别 生日 ID
TOM boy 2021/11/20 1
BOB boy 2021/11/20 2
SYC boy 2021/11/20 3
Sovzn boy 2021/11/20 4
JJK boy 2021/11/20 5
ZS girl 2021/11/20 6
LS girl 2021/11/20 7
WMZ girl 2021/11/20 8
ZH girl 2021/11/20 9
JJK girl 2021/11/20 10
执行结果:
data = Student(name=TOM, gender=boy, birthday=2021-11-20 00:00:00, id=1)
data = Student(name=BOB, gender=boy, birthday=2021-11-20 00:00:00, id=2)
data = Student(name=SYC, gender=boy, birthday=2021-11-20 00:00:00, id=3)
data = Student(name=Sovzn, gender=boy, birthday=2021-11-20 00:00:00, id=4)
data = Student(name=JJK, gender=boy, birthday=2021-11-20 00:00:00, id=5)
data = Student(name=ZS, gender=girl, birthday=2021-11-20 00:00:00, id=6)
data = Student(name=LS, gender=girl, birthday=2021-11-20 00:00:00, id=7)
data = Student(name=WMZ, gender=girl, birthday=2021-11-20 00:00:00, id=8)
data = Student(name=ZH, gender=girl, birthday=2021-11-20 00:00:00, id=9)
data = Student(name=JJK, gender=girl, birthday=2021-11-20 00:00:00, id=10)
Process finished with exit code 0
写操作:
到处数据的实体:
/**
* Description:
* Created by 大师兄 on 2021/11/20 17:22
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ColumnWidth(20) //栏目宽度
//@HeadRowHeight()
public class Student_write {
/**
* id
*/
//@ExcelProperty(value = "编号",index = 3)
@ExcelIgnore
private String id;
/**
* 学生姓名
*/
@ExcelProperty(value = {"信息表","学生姓名"} index = 0)
//@ColumnWidth(30)
private String name;
/**
* 学生性别
*/
@ExcelProperty(value = "学生性别", index = 2)
private String gender;
/**
* 学生出生日期
*/
@ExcelProperty(value = "学生出生日期", index = 1)
@DateTimeFormat("yyyy-MM-dd")
//@ColumnWidth(20)
private Date birthday;
}
/**
* Description:
* Created by 大师兄 on 2021/11/20 17:26
*/
public class StudentWriteDemo {
public static void main(String[] args) {
List<Student_write> Student_writes = initData();
/*
String pathName 写入文件的路径
Class head 写入文件的对象类型
默认写入到07的xlsx中,如果想要写入xls,可以指定类型(待验证)
*/
ExcelWriterBuilder workBook = EasyExcel.write("d:\\测试表.xlsx", Student_write.class);
// sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字
workBook.sheet().doWrite(Student_writes);
}
private static List<Student_write> initData() {
ArrayList<Student_write> Student_writes = new ArrayList<Student_write>();
Student_write data = new Student_write();
for (int i = 0; i < 10; i++) {
data.setName("Sovzn" + i);
data.setBirthday(new Date());
data.setGender("男");
Student_writes.add(data);
}
return Student_writes;
}
}
填充
填充一组数据
Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{、}左右大括号,需要在括号前面使用斜杠转义\{、\}。
代码中用来填充数据的实体对象的成员变量名或用来填充map集合的key需要和Excel中被{}包裹的变量名称一致。
fill_data_template1.xlsx:
/**
* 使用实体类封装填充数据
*
* 实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
*/
@Data
public class FillData {
private String name;
private int age;
}
/**
* Description:
* Created by 大师兄 on 2021/11/20 17:26
*/
public class StudentFillDemo {
public static void main(String[] args) {
// 准备模板
String templateFile = "d:\\JavaFileIOTest\\fill_data_template1.xlsx";
//String templateFile = "fill_data_template1.xlsx";
// 写入文件:当前项目下
String targetFileName = "d:\\JavaFileIOTest\\单组数据填充.xlsx";
// 准备对象数据填充
FillData fillData = new FillData();
fillData.setName("shiyaochang");
fillData.setAge(10);
// 创建一个工作簿对象
ExcelWriterBuilder workBookWriter = EasyExcel.write(targetFileName).withTemplate(templateFile);
// 创建一个工作表
ExcelWriterSheetBuilder sheet = workBookWriter.sheet();
// 获取工作表并填充
sheet.doFill(fillData);
// 使用Map数据填充
//HashMap<String, String> mapFillData = new HashMap<>();
//mapFillData.put("name", "Map");
//mapFillData.put("age", "11");
// 获取第一个工作表填充并自动关闭流
//workBookWriter.sheet().doFill(mapFillData);
}
}
填充多组数据
Excel表格中用{.} 来表示包裹要填充的变量,如果单元格文本中本来就有{.}左右大括号,需要在括号前面使用斜杠转义\{ .\}。
代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。
fill_data_template2.xlsx:
/**
* Description:
* Created by 大师兄 on 2021/11/20 17:26
*/
public class StudentMutiFillDemo {
public static void main(String[] args) {
// 准备模板
String templateFile = "d:\\JavaFileIOTest\\fill_data_template2.xlsx";
//String templateFile = "fill_data_template1.xlsx";
// 写入文件:当前项目下
String targetFileName = "d:\\JavaFileIOTest\\多组数据填充.xlsx";
// 准备对象数据填充
FillData fillData = new FillData();
fillData.setName("shiyaochang");
fillData.setAge(10);
// 创建一个工作簿对象
ExcelWriterBuilder workBookWriter = EasyExcel.write(targetFileName).withTemplate(templateFile);
// 创建一个工作表
ExcelWriterSheetBuilder sheet = workBookWriter.sheet();
List<FillData> fillDatas = initData();
// 获取工作表并填充
sheet.doFill(fillDatas);
}
private static List<FillData> initData() {
ArrayList<FillData> FillDatas = new ArrayList<FillData>();
for (int i = 0; i < 10; i++) {
FillData data = new FillData();
data.setName("Sovzn" + i);
data.setAge(10);
FillDatas.add(data);
}
return FillDatas;
}
}
效果:

组合填充
既有单组数据又有多组数据的填充,注:在EasyExcel中执行doXXX方法后会自动关闭流,因此,组合填充中不能再使用doXXX,方式填充完一组数据后关闭流,使后面的数据无法填充。
fill_data_template3.xlsx:
/**
* Description:
* Created by 大师兄 on 2021/12/06 21:19
*/
public class CombineFillDemo {
public static void main(String[] args) {
// 准备模板
String templateFile = "d:\\JavaFileIOTest\\fill_data_template3.xlsx";
//String templateFile = "fill_data_template1.xlsx";
// 写入文件:当前项目下
String targetFileName = "d:\\JavaFileIOTest\\组合数据填充.xlsx";
// 创建一个工作簿对象(组合填充时,用build创建工作簿)
ExcelWriter workbook = EasyExcel.write(targetFileName).withTemplate(templateFile).build();
// 创建一个工作表
WriteSheet sheet = EasyExcel.writerSheet().build();
//数据(单组):
Map<String, String> DateAndTotal = new HashMap<String, String>() {{
put("date", "2021-1-1");
put("total", "目标001");
}};
//数据(多组):
List<FillData> fillDatas = initData();
// 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行,否则在填充时可能会覆盖下方的单组数据(date和total)
FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
//填充:不适用doXXX,直接使用fill
//1.多组数据填充:
workbook.fill(fillDatas,fillConfig, sheet);
//2.单组数据填充:
workbook.fill(DateAndTotal, sheet);
//切记:手动关流
workbook.finish();
}
private static List<FillData> initData() {
ArrayList<FillData> FillDatas = new ArrayList<FillData>();
for (int i = 0; i < 10; i++) {
FillData data = new FillData();
data.setName("Sovzn" + i);
data.setAge(10);
FillDatas.add(data);
}
return FillDatas;
}
}

水平填充
填充时需要通过FillConfig对象设置水平填充。
fill_data_template4.xlsx:

/**
* Description:
* Created by 大师兄 on 2021/12/06 22:03
*/
public class HORIZONTAL_Fill {
public static void main(String[] args) {
// 准备模板
String templateFile = "d:\\JavaFileIOTest\\fill_data_template4.xlsx";
//String templateFile = "fill_data_template1.xlsx";
// 写入文件:当前项目下
String targetFileName = "d:\\JavaFileIOTest\\水平数据填充.xlsx";
// 创建一个工作簿对象(组合填充时,用build创建工作簿)
ExcelWriter workbook = EasyExcel.write(targetFileName).withTemplate(templateFile).build();
// 创建一个工作表
WriteSheet sheet = EasyExcel.writerSheet().build();
//数据:
List<FillData> fillDatas = initData();
//-------设置水平填充---------
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
//填充
workbook.fill(fillDatas,fillConfig, sheet);
//切记:手动关流
workbook.finish();
}
private static List<FillData> initData() {
ArrayList<FillData> FillDatas = new ArrayList<FillData>();
for (int i = 0; i < 10; i++) {
FillData data = new FillData();
data.setName("Sovzn" + i);
data.setAge(10);
FillDatas.add(data);
}
return FillDatas;
}
}

- Post link: http://sovzn.github.io/2021/11/20/EasyExcel/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.



若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues