工作中总会遇到对Excel读写功能,EasyExcel开源挺久了,但使用上感觉有点让人望而生怯,刚开始看官方文档上读取Excel挺简单的,只需要一行代码[1],继续细看的话还需要创建一个回调监听器[2],有点复杂呀(每个Excel都需要创建一个单独的回调监听器类)。


平常用poi读取excel数据量少,加上EasyExcel读取Excel有点复杂,所以一直也没在项目中使用EasyExcel,直到有一回要读取的数据量太大,使用poi读取Excel在创建Workbook -> WorkbookFactory.create(inputStream) 时就异常了,分配很多内存也不好使,所以放弃使用poi转使用EasyExcel。
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便 --EasyExcel
使用EasyExcel读取Excel时一直在想如何简化读取方式,不用读取每个Excel都创建一个XXDataListene监听器类,刚开始想,把DataListener加上泛型,共用一个DataListener<T>,但是还涉及到如何传递Dao和每个Dao如何保存数据,而且保存数据前可能还需要对数据进行不同的处理。

最后想到了可以用Java8的Consumer + 模板方法设计模式/环绕执行模式/回调(Java8以下可以用匿名内部类),创建一个共用的EasyExcel读取监听器,不在监听器中对数据进行处理,把处理前置。

新建一个读取方法,将调用读取方法时需要创建Listener的步骤隐藏到新方法内,并把Consumer传递到Listener中。*此类继承EasyExcel类。

EasyExcel读取封装后的使用示例:(此处将EasyExcel读取的数据进行了分页,并将原先需在监听器中处理的数据前置到和读取方法同层进行)
Integer pageSize = 7;
ExcelUtil.read(excelFile, DemoData.class, pageSize, pageList -> {
log.info("读取到数据:[size={}]{}", pageList.size(), pageList); // or demoDataService.save(pageList);
}).sheet().doRead();
// 读取到数据:[size=7][DemoData(string=字符串0, date=Wed Jan 01 01:01:01 CST 2020, doubleData=1.0), DemoData(string=字符串1, date=Thu Jan 02 01:01:01 CST 2020, doubleData=2.0), DemoData(string=字符串2, date=Fri Jan 03 01:01:01 CST 2020, doubleData=3.0), DemoData(string=字符串3, date=Sat Jan 04 01:01:01 CST 2020, doubleData=4.0), DemoData(string=字符串4, date=Sun Jan 05 01:01:01 CST 2020, doubleData=5.0), DemoData(string=字符串5, date=Mon Jan 06 01:01:01 CST 2020, doubleData=6.0), DemoData(string=字符串6, date=Tue Jan 07 01:01:01 CST 2020, doubleData=7.0)]
// 读取到数据:[size=3][DemoData(string=字符串7, date=Wed Jan 08 01:01:01 CST 2020, doubleData=8.0), DemoData(string=字符串8, date=Thu Jan 09 01:01:01 CST 2020, doubleData=9.0), DemoData(string=字符串9, date=Fri Jan 10 01:01:01 CST 2020, doubleData=10.0)]
参考:EasyExcel官网读取示例代码
EasyExcel分页读取完整代码实现:
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.function.Consumer;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
/**
* Excel工具类.
* @author www@yiynx.cn
*/
public class ExcelUtil extends EasyExcel {
private ExcelUtil() {}
public static <T> ExcelReaderBuilder read(String pathName, Class<T> head, Integer pageSize, Consumer<List<T>> consumer) {
return read(pathName, head, new EasyExcelConsumerListener<>(pageSize, consumer));
}
public static <T> ExcelReaderBuilder read(File file, Class<T> head, Integer pageSize, Consumer<List<T>> consumer) {
return read(file, head, new EasyExcelConsumerListener<>(pageSize, consumer));
}
public static <T> ExcelReaderBuilder read(InputStream inputStream, Class<T> head, Integer pageSize, Consumer<List<T>> consumer) {
return read(inputStream, head, new EasyExcelConsumerListener<>(pageSize, consumer));
}
}
EasyExcelConsumerListener.java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
/**
* EasyExcel消费监听.
* @author www@yiynx.cn
* @param <T>
* see <a href="https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java">DemoDataListener</a>
*/
public class EasyExcelConsumerListener<T> extends AnalysisEventListener<T> {
private int pageSize;
private List<T> list;
private Consumer<List<T>> consumer;
public EasyExcelConsumerListener(int pageSize, Consumer<List<T>> consumer) {
this.pageSize = pageSize;
this.consumer = consumer;
list = new ArrayList<>(pageSize);
}
@Override
public void invoke(T data, AnalysisContext context) {
list.add(data);
if (list.size() >= pageSize) {
consumer.accept(list);
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
consumer.accept(list);
}
}
- 另1:还可以在ExcelUtil中定义一个默认分页大小, 再次简化调用(省略pageSize参数)。
ExcelUtil.read(excelFile, DemoData.class, pageList -> {
System.out.println(pageList.size());
}).sheet().doRead(); // 默认分页大小
- 另2:可以封装一个Page类,不直接返回pageList,而是返回page对象,这样可以通过page类知道当前是第几页,共几页,总条数等,方便输出处理进度(向前端推送读取Excel的处理进度)。
说明:从30行到1行
简化后使用示例:
ExcelUtil.read(excelFile, DemoData.class, pageSize, pageList -> dataService.save(pageList)).sheet().doRead();
简化前使用示例(简化版-对读取监听器代码[2]进行了删减部分注释代码后代码30行左右):
EasyExcel.read(fileName, DemoData.class, new DemoDataListener(dataService)).sheet().doRead();
// DemoDataListener.java
package com.test.test.demo.read;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
/**
* 模板的读取类
* @author https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java
*/
public class DemoDataListener extends AnalysisEventListener<DemoData> {
private static final int BATCH_COUNT = 5;
private List<DemoData> list = new ArrayList<>();
private DemoService demoService;
public DemoDataListener(DemoService demoService) {
this.demoService = demoService;
}
@Override
public void invoke(DemoData data, AnalysisContext context) {
list.add(data);
if (list.size() >= BATCH_COUNT) {
saveData();
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
}
private void saveData() {
demoService.save(list);
}
}
关键词:EasyExcel分页读取;EasyExcel读取封装;EasyExcel读取简化;EasyExcel读取处理前置;EasyExcel读取监听器泛型;EasyExcel DataListener 泛型;Java8;Consumer;
《大话设计模式》京东 电子书 ¥9.99e.jd.com

对于编程思想和能力有重大提升的书有哪些? - 似懂非懂风格的话的回答 - 知乎 https://www.zhihu.com/question/35648714/answer/65081192
参考
- ^EasyExcel-读Excel https://alibaba-easyexcel.github.io/index.html
- ^abDemoDataListener.java https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java