文章摘自:https://blog.csdn.net/caijwjava/article/details/100855361
实战
1、导入一个相关依赖即可
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> </dependency>
2、读取(上传)Excel文件,使用上非常的简单,只要几行代码。
public class EasyExcelDemo { public static void main(String[] args) { String path = "E:\\tmp\\demo.xlsx"; // 自定义读取每一行数据的事件监听 ReadListen readListen = new ReadListen(); // 开始读取Excel数据 ExcelReader reader = EasyExcel.read(path, readListen).build(); // 读取第一个sheet,readSheet默认是读取第一个,可以自定义 ReadSheet readSheet = EasyExcel.readSheet().build(); // 开始读取数据 reader.read(readSheet); // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 reader.finish(); } }
3、自定义ReadListen,主要是为了装数据,方便获取和使用,具体怎么调到这个方法的,以及出现的问题点在哪,往下走。。。
public class ReadListen<T> extends AnalysisEventListener<T> { private List<T> sheetData = new ArrayList<>(10); @Override public void invoke(T t, AnalysisContext analysisContext) { sheetData.add(t); System.out.println(t); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { // Excel数据解析完成后,想要实现的业务逻辑 System.out.println("excel data resolve finish!!!"); } }
4、开始源码分析,easyexcel怎么读取数据,以及问题点分析解决!
4.1 当调用reader.read(readSheet);时,进入
public ExcelReader read(ReadSheet readSheet) { checkFinished(); // 开始进入解析sheet excelAnalyser.analysis(readSheet); return this; }
我们只要分析excelAnalyser.analysis(readSheet);,进入继续
analysisContext.currentSheet(excelExecutor, readSheet); try { // 真正解析sheet表格的地方 excelExecutor.execute(); } catch (ExcelAnalysisStopException e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Custom stop!"); } } // 这个是在sheet表格解析完成后回调的方法,参考前面自定义的ReadListen analysisContext.readSheetHolder().notifyAfterAllAnalysed(analysisContext);
点击进入
@Override public void execute() { parseXmlSource(sheetMap.get(analysisContext.readSheetHolder().getSheetNo()), // 这个就是我们真正关心的地方,每行解析完成后会执行的操作 new XlsxRowHandler(analysisContext, stylesTable)); }
点击进入,继续
public XlsxRowHandler(AnalysisContext analysisContext, StylesTable stylesTable) { // 这个是每个单元格执行的一些操作,包括回调之类的 this.cellHandlers = XlsxHandlerFactory.buildCellHandlers(analysisContext, stylesTable); // ... }
点击进去,这时我们会注意到notifyEndOneRow,这个正是需要关注的点,继续
@Override public void endHandle(String name) { analysisContext.readSheetHolder() // 这个就是每一行表格数据解析完成后会触发的回调 .notifyEndOneRow(new EachRowAnalysisFinishEvent(rowResultHandler.getCurRowContent()), analysisContext); rowResultHandler.clearResult(); }
点击继续,看看里面做了什么操作。。。
@Override public void notifyEndOneRow(AnalysisFinishEvent event, AnalysisContext analysisContext) { // ...to do something if (rowIndex >= headRowNumber) { // Now is data for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { try { // 真正触发回调的地方,这里会有多个监听,包括我们前面自定义的ReadListen readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext); // ...
现在慢慢的明朗了整个读取流程,在debug之后,看看每个Listenner之后,找到一个ModelBuildEventListener,这个就是Excel数据转换的Listenner,也就是前面说到的会导致Excel表格列数据为空的时候,导致位置发生变化的地方,具体看看这里做了什么?
private Object buildStringList(Map<Integer, CellData> cellDataMap, ReadHolder currentReadHolder, AnalysisContext context) { // 这里是为了兼容一些旧的代码,做了一个映射,是返回list还是map if (context.readWorkbookHolder().getDefaultReturnMap()) { // ...省略不需要关心的代码 return map; } else { List<String> list = new ArrayList<String>(); for (Map.Entry<Integer, CellData> entry : cellDataMap.entrySet()) { // ...省略不需要关心的代码 return list; } }
在进入context.readWorkbookHolder().getDefaultReturnMap())的时候,在新的版本中默认是true,这样返回map的时候就不会出现前面说的列数据对不上的问题。下面看看返回Map和List有什么区别。
使用List返回的数据示例:
使用Map返回的数据示例:
通过对比,我们就很快可以发现使用List缺少了序号而导致无法把数据对应上,而使用Map的时候,key就是序号,value是我们需要的单元格数据。
那么,这个开关在哪里呢?细心的应该看到了,如果你也存在这个问题,又不想升级版本等,可以使用一个开关设置成Map再转换成自己需要的List集合即可。或者说不想用Map,那么都可以自己设置。
public static void main(String[] args) { // ...省略 ReadSheet readSheet = EasyExcel.readSheet().build(); // 具体的设置开关,新的的版本中默认true reader.analysisContext().readWorkbookHolder().setDefaultReturnMap(true); // 开始读取数据 reader.read(readSheet); // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 reader.finish(); }
在前面的代码中,添加一句reader.analysisContext().readWorkbookHolder().setDefaultReturnMap(true);,注意这里默认就是true,可以设置为false,返回的数据就是list了。