EasyExcel 学习笔记 - 读Excel

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EasyExcel 学习笔记 - 读Excel相关的知识,希望对你有一定的参考价值。

EasyExcel 学习笔记 - 读Excel

基本上就是官方的快速开始,自己走了一遍,并笔记。没有完全照抄官方示例,正好可以做对比。

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(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

  1. 我们使用自带的监听器 PageReadListener ,它每次会读取BATCH_COUNT = 100条数据。然后进行消费。
  2. invoke:每一读取完毕会调用。
  3. doAfterAllAnalysed:每个sheet读取完毕会调用。
  4. 我们创建时要给它传个消费者Consumer<List<T>> consumer。(invokedoAfterAllAnalysed会调用消费者)
  5. 这里需要指定所使用的实体类型 DemoData.class ,读取sheet(默认索引0)后,文件流会自动关闭。
  6. 以下就是官网的例了,我只是把写法调整了一下:
  7. ReadWorkbook 对应一个 excelEasyExcel.read()返回的 ExcelReaderBuilder 可以配置。
  8. 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

写法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

EasyExcel 学习笔记 - 自定义注解导入 Excel

EasyExcel 专题 深度解析读流程核心源码

EasyExcel 专题 深度解析读流程核心源码

EasyExcel 专题 深度解析读流程核心源码