EasyExcel 学习笔记 - 读Excel
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EasyExcel 学习笔记 - 读Excel相关的知识,希望对你有一定的参考价值。
EasyExcel 学习笔记 - 读Excel
- pom.xml 添加依赖
- 测试数据
- 最简单的读
- 多行头(跳过N行)
- 读多个sheet
- 同步的返回
- 读取表头数据
- 额外信息(批注、超链接、合并单元格信息读取)
- 读取公式和单元格类型
- 数据转换等异常处理
- 不创建对象的读
- web中的读
- 参考资料
基本上就是官方的快速开始,自己走了一遍,并笔记。没有完全照抄官方示例,正好可以做对比。
pom.xml 添加依赖
另外为了方便还引入了fastjson 2.0.3
, lombok 1.18.24
, junit 4.11
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
测试数据
测试的 excel 放在此处:
private String filePath = "E:\\\\temp\\\\测试数据.xlsx";
实体类
- @ExcelProperty 可设置字段。
用法 | 说明 |
---|---|
@ExcelProperty(index = 0) | 指定列的索引 |
@ExcelProperty("日期标题") | 指定列名 |
@ExcelProperty(converter = CustomStringStringConverter.class) | 自定义转换器 |
@DateTimeFormat("yyyy-MM-dd HH:mm:ss") | string 接格式化的日期 |
@NumberFormat("#.##%") | string 接格式的化百分比数字 |
@Data
@EqualsAndHashCode
public class DemoData
// @ExcelProperty(index = 0)
private String string;
// @ExcelProperty("日期标题")
private Date date;
// @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
// private String dateStr;
private Double doubleData;
自定义转换器
public class CustomStringStringConverter implements Converter<String>
@Override
public Class<?> supportJavaTypeKey()
return String.class;
@Override
public CellDataTypeEnum supportExcelTypeKey()
return CellDataTypeEnum.STRING;
/** 这里读的时候会调用 */
@Override
public String convertToJavaData(ReadConverterContext<?> context)
return "自定义:" + context.getReadCellData().getStringValue();
/** 这里是写的时候会调用 */
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context)
return new WriteCellData<>(context.getValue());
最简单的读
写法1
- 我们使用自带的监听器 PageReadListener ,它每次会读取
BATCH_COUNT = 100
条数据。然后进行消费。 invoke
:每一行
读取完毕会调用。doAfterAllAnalysed
:每个sheet
读取完毕会调用。- 我们创建时要给它传个消费者
Consumer<List<T>> consumer
。(invoke
、doAfterAllAnalysed
会调用消费者) - 这里需要指定所使用的实体类型
DemoData.class
,读取sheet
(默认索引0
)后,文件流会自动关闭。 - 以下就是官网的例了,我只是把写法调整了一下:
- ReadWorkbook 对应一个
excel
。EasyExcel.read()
返回的 ExcelReaderBuilder 可以配置。 - ReadSheet 对应
sheet
。.sheet()
返回的 ExcelReaderSheetBuilder 可以配置。
@Test
public void simpleRead()
// 这个消费者在 PageReadListener 的 invoke、doAfterAllAnalysed 中会被调用。
Consumer<List<DemoData>> consumer = dataList ->
dataList.forEach(demoData -> log.info("读取到一条数据", JSON.toJSONString(demoData)));
;
// 用上面的消费者创建监听器
PageReadListener<DemoData> myListener = new PageReadListener<>(consumer);
// 开始读取
EasyExcel.read(fileName, DemoData.class, myListener).sheet().doRead();
写法2
- 直接用
匿名类
实现 ReadListener接口。重写了invoke
、doAfterAllAnalysed
这两个方法就行了。 - 官方示例代码有点长:略。
写法3
具名类
实现 ReadListener接口得到一个监听器。和上面只是具名类
与匿名类
的区别。
写法4
一个文件一个 ExcelReader
// 这里直接用了前文中的监听器 myListener
try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, myListener ).build())
// 构建一个sheet 这里可以指定名字或者索引(注意我给的索引是第二个sheet)
ReadSheet readSheet = EasyExcel.readSheet(1).build();
// 读取这个sheet
excelReader.read(readSheet);
多行头(跳过N行)
如果头有多行,那么可以指定跳过多少行,开始读取。
只要加了一个 headRowNumber
用于说明头有多少行。
@Test
public void headRead()
EasyExcel.read(filePath, new NoModelDataListener()).sheet().headRowNumber(7).doRead();
读多个sheet
节约篇幅我这里直接用了 PageReadListener。
这里需要注意:每个sheet
读取完毕后调用一次doAfterAllAnalysed
。
因为这里只给了一个监听器,最后所有sheet
都会写进同一个监听器
里。(直观的效果就是如下代码每次输出都包含上一次的内容。人个理解是要处理数据的话,在读完最后一个sheet时再一起处理就好了。)
读全部 sheet
EasyExcel.read(fileName, DemoData.class, new PageReadListener<>(dataList ->
dataList.forEach(demoData -> log.info("读取到一条数据", JSON.toJSONString(demoData)));
)).doReadAll();
读部分 sheet
这里读取第1
与第3
个表,每个sheet
对应各自的监听器
,所以是分开输出的。
如果用同一个监听器
,则效果与上面一样。
// 监听器
Consumer<List<DemoData>> consumer = dataList ->
dataList.forEach(demoData -> log.info("读取到一条数据", JSON.toJSONString(demoData)));
;
// 用上面的监听器,生成 ReadSheet 列表。(第1、第3 个Sheet)
List<ReadSheet> sheets = Stream.of(0, 2)
.map(i -> EasyExcel.readSheet(i)
.head(DemoData.class)
.registerReadListener(new PageReadListener<>(consumer))
.build())
.collect(Collectors.toList());
// 读取 sheets 中的表
try (ExcelReader excelReader = EasyExcel.read(fileName).build())
excelReader.read(sheets);
同步的返回
与最简单的读
的区别只是把doRead
换成 doReadSync
。
下面是不带head(DemoData.class)
的写法。直接用List<Map<Integer, String>>
接。
List<Map<Integer, String>> objects = EasyExcel.read(fileName).sheet().doReadSync();
objects.forEach(demoData -> log.info("读取到一条数据", JSON.toJSONString(demoData)));
读取表头数据
懒得从头实现一个监听器,直接用 SyncReadListener 复写 nvokeHeadMap
。
这里设置了headRowNumber(3)
头有3行
。
SyncReadListener syncReadListener = new SyncReadListener()
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context)
log.info("解析到一条头数据:", JSON.toJSONString(headMap));
;
EasyExcel.read(fileName, DemoData.class, syncReadListener).sheet().headRowNumber(3).doRead();
额外信息(批注、超链接、合并单元格信息读取)
官网示例代码 中按类型分别判断并处理有点长,我这直接输出就好了。
同样偷个懒重写 SyncReadListener 直接用。
SyncReadListener syncReadListener = new SyncReadListener()
@Override
public void extra(CellExtra extra, AnalysisContext context)
log.info("\\n额外信息的类型(批注、超链接、合并单元格)是:;" +
"\\n行:,列;\\n开始行:,开始列;\\n结束行:,结束例:;\\n内容为:",
extra.getType(),
extra.getRowIndex(), extra.getColumnIndex(),
extra.getFirstRowIndex(), extra.getFirstColumnIndex(),
extra.getLastRowIndex(), extra.getLastColumnIndex(),
extra.getText());
;
// 三种额外信息默认都是不读取,需要手动添加。
EasyExcel.read(fileName, null, syncReadListener)
.extraRead(CellExtraTypeEnum.COMMENT)// 读取批注
.extraRead(CellExtraTypeEnum.HYPERLINK)// 读取超链接
.extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();// 读取合并单元格信息
读取公式和单元格类型
关键在于实体中的字体用 CellData 进行包裹,就能拿到单元格信息了。
注意:字段顺序要与表格列对应
@Data
@EqualsAndHashCode
@JSONType(orders = "string", "date", "doubleData", "formulaValue") // 设置下顺序好看些
public class CellDataReadDemoData
private CellData<String> string;
private CellData<Date> date; // excel 中的日期值其实是数字
private CellData<Double> doubleData;
private CellData<String> formulaValue; // 不一定能完美的获取,等官方后续
List<CellDataReadDemoData> objects = EasyExcel.read(fileName).head(CellDataReadDemoData.class).sheet().doReadSync();
objects.forEach(demoData -> log.info("解析到一条数据", JSON.toJSONString(demoData)));
-
这里表的内容
-
这是一行的效果
"string":
"data":"字符串0","dataFormatData":"format":"General","index":0,"stringValue":"字符串0","type":0
,
"date":
"data":"2022-06-27 00:00:00","dataFormatData":"format":"yyyy/m/d","index":14,"numberValue":44739.0,"type":2
,
"doubleData":
"data":1.0,"dataFormatData":"format":"General","index":0,"numberValue":1.0,"type":2
,
"formulaValue":
"data":"字符串0447391","dataFormatData":"format":"General","index":0,
"formulaData":"formulaValue":"A2&B2&C2","stringValue":"字符串0447391","type":0
数据转换等异常处理
@Data
@EqualsAndHashCode
public class ExceptionDemoData
private Date date;
ReadListener<ExceptionDemoData> readListener = new ReadListener<ExceptionDemoData>()
@Override
public void invoke(ExceptionDemoData data, AnalysisContext context)
@Override
public void doAfterAllAnalysed(AnalysisContext context)
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception
log.info("解析失败,但是继续解析下一行:", exception.getMessage());
;
List<ExceptionDemoData> objects = EasyExcel.read(fileName, ExceptionDemoData.class, readListener) .sheet().doReadSync();
objects.forEach(demoData -> log.info("解析到一条数据", JSON.toJSONString(demoData)));
System.out.println("正常结束");
官方说的是在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
但我还是被终断了执行,不知道是不是我理解的不对。
不创建对象的读
正常创建按表字段,创建实体,然后读数据,没什么好说的。这里看一下:不创建对象的读
监听器
就是官方代码,只是补充了一下注释。
package com.jerry.excel.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
@Slf4j
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>>
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 每一行读取完毕会调用
* @param data 当前行数据
* @param context
*/
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context)
log.info("解析到一条数据:", JSON.toJSONString(data));
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT)
saveData();
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 每个sheet读取完毕会调用
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context)
saveData();
log.info("所有数据解析完成!");
/**
* 写入数据库操作
*/
private void saveData()
log.info("条数据,开始存储数据库!", cachedDataList.size());
log.info("存储数以上是关于EasyExcel 学习笔记 - 读Excel的主要内容,如果未能解决你的问题,请参考以下文章
poi和easyExcel基于Java操作Excel学习笔记
EasyExcel 学习笔记 - 自定义注解导入 Excel