EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
github地址: https://github.com/alibaba/easyexcel
官方文档: https://www.yuque.com/easyexcel/doc/easyexcel
Excel解析流程图:
EasyExcel读取Excel的解析原理:
添加maven依赖, 依赖的poi最低版本3.17
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.3</version>
</dependency>
Excel数据类型
字符串标题 | 日期标题 | 数字标题 |
---|---|---|
小明 | 2020-05-05 10:10:10 | 888.88 |
数据模板
注意: Java类中的属性字段顺序和Excel中的表头字段顺序一致, 可以不写@ExcelProperty
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DemoData {
// 根据Excel中指定列名或列的索引读取
@ExcelProperty(value = "字符串标题", index = 0)
private String name;
@ExcelProperty(value = "日期标题", index = 1)
private Date hireDate;
@ExcelProperty(value = "数字标题", index = 2)
private Double salary;
// lombok 会生成getter/setter方法
}
读取excel代码
关键是写一个监听器,实现AnalysisEventListener, 每解析一行数据会调用invoke方法返回解析的数据, 当全部解析完成后会调用doAfterAllAnalysed方法. 我们重写invoke方法和doAfterAllAnalysed方法即可.
@Test
public void testReadExcel() {
// 读取的excel文件路径
String filename = "D:\\study\\excel\\read.xlsx";
// 读取excel
EasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() {
// 每解析一行数据,该方法会被调用一次
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("解析数据为:" + demoData.toString());
}
// 全部解析完成被调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("解析完成...");
// 可以将解析的数据保存到数据库
}
}).sheet().doRead();
}
效果:
读excel的方式二代码
@Test
public void testReadExcel2() {
// 读取的excel文件路径
String filename = "D:\\study\\excel\\read.xlsx";
// 创建一个数据格式来装读取到的数据
Class<DemoData> head = DemoData.class;
// 创建ExcelReader对象
ExcelReader excelReader = EasyExcel.read(filename, head, new AnalysisEventListener<DemoData>() {
// 每解析一行数据,该方法会被调用一次
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("解析数据为:" + demoData.toString());
}
// 全部解析完成被调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("解析完成...");
// 可以将解析的数据保存到数据库
}
}).build();
// 创建sheet对象,并读取Excel的第一个sheet(下标从0开始), 也可以根据sheet名称获取
ReadSheet sheet = EasyExcel.readSheet(0).build();
// 读取sheet表格数据, 参数是可变参数,可以读取多个sheet
excelReader.read(sheet);
// 需要自己关闭流操作,在读取文件时会创建临时文件,如果不关闭,会损耗磁盘,严重的磁盘爆掉
excelReader.finish();
}
要读取的源数据, 日期格式是yyyy年MM月dd日 HH时mm分ss秒, 数字带小数点
数据模板
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DemoData {
@ExcelProperty(value = "字符串标题", index = 0)
private String name;
@ExcelProperty(value = "日期标题", index = 1)
// 格式化日期类型数据
@DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒")
private Date hireDate;
@ExcelProperty(value = "数字标题", index = 2)
// 格式化数字类型数据,保留一位小数
@NumberFormat(value = "###.#")
private String salary;
//注意: @NumberFormat对于Double类型的数据格式化会失效,建议使用String类型接收数据进行格式化
// private Double salary;
// lombok 会生成getter/setter方法
}
读取excel代码同上面读取方式一样.
读方式一, 使用ExcelReaderBuilder#doReadAll方法
@Test
public void testReadExcel() {
// 读取的excel文件路径
String filename = "D:\\study\\excel\\read.xlsx";
// 读取excel
EasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() {
// 每解析一行数据,该方法会被调用一次
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("解析数据为:" + demoData.toString());
}
// 全部解析完成被调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("解析完成...");
// 可以将解析的数据保存到数据库
}
})
// .sheet(0).doRead();
.doReadAll(); // 读取全部sheet
}
读方式二, 使用ExcelReader#readAll方法
@Test
public void testReadExcel2() {
// 读取的excel文件路径
String filename = "D:\\study\\excel\\read.xlsx";
// 创建一个数据格式来装读取到的数据
Class<DemoData> head = DemoData.class;
// 创建ExcelReader对象
ExcelReader excelReader = EasyExcel.read(filename, head,
new AnalysisEventListener<DemoData>() {
// 每解析一行数据,该方法会被调用一次
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("解析数据为:" + demoData.toString());
}
// 全部解析完成被调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("解析完成...");
// 可以将解析的数据保存到数据库
}
}).build();
// 创建sheet对象,并读取Excel的第一个sheet(下标从0开始), 也可以根据sheet名称获取
ReadSheet sheet = EasyExcel.readSheet(0).build();
// 读取sheet表格数据 , 参数是可变参数,可以读取多个sheet
// excelReader.read(sheet);
excelReader.readAll(); // 读所有sheet
// 需要自己关闭流操作,在读取文件时会创建临时文件,如果不关闭,会损耗磁盘,严重的磁盘爆掉
excelReader.finish();
}
不同sheet表格的数据模板可能不一样,这时候就需要分别构建不同的sheet对象,分别为其指定对应的数据模板.
@Test-
public void testReadExcel3() {
// 读取的excel文件路径
String filename = "D:\\study\\excel\\read.xlsx";
// 构建ExcelReader对象
ExcelReader excelReader = EasyExcel.read(filename).build();
// 构建sheet对象
ReadSheet sheet0 = EasyExcel.readSheet(0)
.head(DemoData.class) // 指定sheet0的数据模板
.registerReadListener(new AnalysisEventListener<DemoData>() {
// 每解析一行数据,该方法会被调用一次
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("解析数据为:" + demoData.toString());
}
// 全部解析完成被调用
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("解析完成...");
// 可以将解析的数据保存到数据库
}
}).build();
// 读取sheet,有几个就构建几个sheet进行读取
excelReader.read(sheet0);
// 需要自己关闭流操作,在读取文件时会创建临时文件,如果不关闭,会损耗磁盘,严重的磁盘爆掉
excelReader.finish();
}
上面章节的读取Excel的程序弊端:
每次解析不同数据模型都要新增一个监听器, 重复工作量大;
即使用了匿名内部类,程序也显得臃肿;
数据处理一般都会存在于项目的service中, 监听器难免会依赖dao层, 导致程序耦合度高.
解决方案:
通过泛型指定数据模型类型, 针对不同类型的数据模型只需要定义一个监听器即可;
使用jdk8新特性中的函数式接口, 将数据处理从监听器中剥离出去, 进行解耦. 监听器代码:
/**
* 类描述:easyexcel工具类
* @Author
* @Date
* @Version 1.0
*/
public class EasyExcelUtils<T> {
/**
* 获取读取Excel的监听器对象
* 为了解耦及减少每个数据模型bean都要创建一个监听器的臃肿, 使用泛型指定数据模型类型
* 使用jdk8新特性中的函数式接口 Consumer
* 可以实现任何数据模型bean的数据解析, 不用重复定义监听器
* @param consumer 处理解析数据的函数, 一般可以是数据入库逻辑的函数
* @param threshold 阈值,达到阈值就处理一次存储的数据
* @param <T> 数据模型泛型
* @return 返回监听器
*/
public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer, int threshold) {
return new AnalysisEventListener<T>() {
/**
* 存储解析的数据 T t
*/
// ArrayList基于数组实现, 查询更快
// List<T> dataList = new ArrayList<>(threshold);
// LinkedList基于双向链表实现, 插入和删除更快
List<T> dataList = new LinkedList<>();
/**
* 每解析一行数据事件调度中心都会通知到这个方法, 订阅者1
* @param data 解析的每行数据
* @param context
*/
@Override
public void invoke(T data, AnalysisContext context) {
dataList.add(data);
// 达到阈值就处理一次存储的数据
if (dataList.size() >= threshold) {
consumer.accept(dataList);
dataList.clear();
}
}
/**
* excel文件解析完成后,事件调度中心会通知到该方法, 订阅者2
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 最后阈值外的数据做处理
if (dataList.size() > 0) {
consumer.accept(dataList);
}
}
};
}
/**
* 获取读取Excel的监听器对象, 不指定阈值, 默认阈值为 2000
* @param consumer
* @param <T>
* @return
*/
public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>>
consumer) {
return getReadListener(consumer, 2000);
}
}
再来看读取Excel的 代码:
/**
* 采用解耦的自定义监听器读取Excel, 可以实现任何数据模型bean的读取
*/
@Test
public void testReadExcelN() {
// 读取的excel文件路径
String filename = "D:\\study\\excel\\user1.xlsx";
// 读取excel
EasyExcel.read(filename, UserModel.class,
EasyExcelUtils.getReadListener(dataProcess()))
.doReadAll(); // 读取全部sheet
}
/**
* 传给监听器的是一个处理解析数据的函数, 当调用consumer的accept方法时就会调用传递的函数逻辑
* 这里传递的函数是对解析结果集的遍历打印操作, 也可以是数据入库操作
* @return
*/
public Consumer<List<UserModel>> dataProcess() {
Consumer<List<UserModel>> consumer = users -> users.forEach(System.out::println);
return consumer;
}
参考网址:
https://blog.csdn.net/u013044713/article/details/120249233
https://github.com/alibaba/easyexcel
https://www.jianshu.com/p/10e05e89b8a0
http://events.jianshu.io/p/52d9e12e93bd