EasyExcel读文件详解和源码分析

读取文件导入的话,我们经常看到下面这些方法。

//同步的返回,不推荐使用
EasyExcel.read(file).sheet(sheetNo).head(Class.class).headRowNumber(headRowNum)..doReadSync(); 

//异步的,通过监听器处理读到的数据。
EasyExcel.read(file).sheet(sheetNo).head(Class.class).headRowNumber(headRowNum).registerReadListener(监听器)doRead(); 

首先建议大家可以看一下 EasyExcel为我们提供的 EasyExcelFactory工厂类相关的源代码,看懂源代码的话,对我们自己编写适合自己项目的方法很用用处。

引入依赖:

        <!--   easyexcel 3.1.0+版本不需要poi依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.1.1</version>
        </dependency>

一、EasyExcel读取文件源码

1、EasyExcelFactory工厂类

方法:EasyExcel.read(file)

查看 read()方法:

在这里插入图片描述

EasyExcel类调用 read方法,实际调用的是 EasyExcelFactory类的方法。

查看 EasyExcelFactory类方法:

在这里插入图片描述

EasyExcelFactory工厂类定义了许多读和写的重载方法。主要看读方法,分为两类:

  • 读取Excel文件,返回 ExcelWriterBuilder类
  • 读取Excel文件中的sheet,返回 ExcelReaderSheetBuilder类

从这里我们可以看出,EasyExcelFactory工厂类创建了 XxxBuilder类,并返回了 XxxBuilder类,那是不是我们也可以直接使用 XxxBuilder类操作读文件。答案是肯定的。

2、ExcelWriterBuilder类

查看 ExcelReaderBuilder类:

在这里插入图片描述

ExcelReaderBuilder类实例化时,创建了 ReadWorkbook对象。

查看 ExcelReaderBuilder类方法:

在这里插入图片描述

ExcelReaderBuilder类主要处理 Excel文件和相关文件属性信息,比如设置 字符编码、文件加密的密码,忽略处理哪行数据等。

3、ExcelReaderSheetBuilder类

查看 ExcelReaderSheetBuilder类:

在这里插入图片描述

ExcelReaderSheetBuilder类实例化时,创建了 ReadSheet对象和 ExcelReader对象。

查看 ExcelReaderSheetBuilder类方法:

在这里插入图片描述

ExcelReaderSheetBuilder类主要处理 Excel文件中的每一个 sheet信息,比如设置要读取的sheet名,索引。

调用 doRead()方法其实底层通过 ExcelReader对象读取每一个 sheet信息。

4、ExcelReader类

查看 ExcelReader类:

在这里插入图片描述

查看 ExcelReader类方法:

在这里插入图片描述
这里主要看一下 read方法。

在这里插入图片描述

可以看出 ExcelReader类包含了 ReadWorkbook对象和 ReadSheet对象。从而处理 Excel文件中的每一个 sheet信息。

思考:

(1)ExcelReader类是如何初始化的?

在 ExcelReaderBuilder类调用 sheet()方法时,初始化了 ExcelReader对象。

在这里插入图片描述

在 ExcelReaderSheetBuilder类调用 doRead()方法时,底层就通过 ExcelReader对象遍历读取每一个 sheet信息。

在这里插入图片描述

(2)ReadWorkbook对象和 ReadSheet对象是如何赋值给 ExcelReader类的?

在各自的XxxBuilder类中通过构造方法初始化时,创建的,然后分别在调用 sheet()方法和 doRead()方法各自的 build()方法中完成赋值的。

有了上面的知识,接下来通过 EasyExcel操作读 Excel文件就比较简单了。

二、读一个sheet

一般情况,我们创建一个对象来和 Excel文件sheet的列名建立映射关系。

指定列的下标或者列名:

  • 在字段上添加 @ExcelProperty注解,不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配。

自定义格式转换:

  • 可以自定义格式转换器,也可以使用自带的日期、数字格式转换。

下面我们创建一个Excel文件。

在这里插入图片描述

根据 Sheet信息创建一个映射类:

@Getter
@Setter
@EqualsAndHashCode
public class DemoData {

    @ExcelProperty("标题")
    private String string;

    @ExcelProperty("日期")
    private Date date;

    @ExcelProperty("浮点数据")
    private Double doubleData;

}

1、同步读

同步读并返回数据,不推荐使用。

    public static void main(String[] args) {
        syncRead();
    }

    private static void syncRead() {
        String fileName = "D:\\TempFiles\\表格.xlsx";
        List<DemoData> demoDataList = EasyExcel.read(fileName)
                .sheet()
                .head(DemoData.class)
                .headRowNumber(1)
                .doReadSync(); //同步读
        log.info("同步解析到所有数据为:{}", JSON.toJSONString(demoDataList));
    }

2、异步读

注册一个自带的匿名监听器。

    public static void asyncRead() {
        String fileName = "D:\\TempFiles\\表格.xlsx";
        EasyExcel.read(fileName)// 读取Excel文件
                .sheet(0) // 读取哪个sheet,索引从0开始
                .head(DemoData.class) // 设置映射对象
                .headRowNumber(1) // 设置1,因为头值占了一行。如果多行头,就设置几行。索引从1开始
                .registerReadListener(new AnalysisEventListener<DemoData>() { //注册读的监听器
                    /**
                     * 每解析一行excel数据,就会被调用一次
                     * @param demoData
                     * @param analysisContext
                     */
                    @Override
                    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
                        log.info("解析到一条数据为:{}", JSON.toJSONString(demoData));
                    }

                    /**
                     * 全部解析完被调用
                     * @param analysisContext
                     */
                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        log.info("全部解析完成");
                    }
                })
                .doRead();
    }

在这里插入图片描述

三、读多个sheet

监听器可以理解为是对读取的数据进行校验(空校验,类型校验)和处理的逻辑部分,用于异步读取。

在上面异步读中的代码,使用了一个参数就是 AnalysisEventListener<T> excelListener的监听器。

AnalysisEventListener类实现了 ReadListener接口,ReadListener中有下面几个方法:

public interface ReadListener<T> extends Listener {
 
    // 在转换异常获取其他异常下会调用本接口。
    default void onException(Exception exception, AnalysisContext context) throws Exception {
        throw exception;
    }
 
    //读取表头数据存在headMap中
    default void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {}
 
    //读取一行一行数据到data
    void invoke(T data, AnalysisContext context);
 
    void extra(CellExtra var1, AnalysisContext context);
 
    //在完成所有数据解析后进行的操作。AOP思想。
    void doAfterAllAnalysed(AnalysisContext context);
 
    default boolean hasNext(AnalysisContext context) {
        return true;
    }
}

我们可以根据进行自己需求的扩展 AnalysisEventListener这个类或者ReadListener接口,对那几个方法进行扩展。

在上面的Excel文件中我们再创建一个Sheet。

在这里插入图片描述

对应再创建一个映射类:

@Getter
@Setter
@EqualsAndHashCode
public class DemoData2 {

    @ExcelProperty("标题")
    private String string;

    @ExcelProperty("日期")
    private Date date;

    @ExcelProperty("浮点数据")
    private Double doubleData;

    @ExcelProperty("整数")
    private Integer integerData;

    /**
     * Java String类型会丢失精度,建议定义为 BigDecimal|Double类型
     */
    @ExcelProperty("经度")
    private Double longitude;

    @ExcelProperty("纬度")
    private Double latitude;

}

1、使用匿名监听器

    public static void manySheetRead() {
        String fileName = "D:\\TempFiles\\表格.xlsx";
        ExcelReader excelReader = EasyExcel.read(fileName).build();

        ReadSheet readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).headRowNumber(1)
                .registerReadListener(new AnalysisEventListener<DemoData>() { //注册读的监听器
                    /**
                     * 每解析一行excel数据,就会被调用一次
                     * @param demoData
                     * @param analysisContext
                     */
                    @Override
                    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
                        log.info("readSheet1 解析到一条数据为:{}", JSON.toJSONString(demoData));
                    }

                    /**
                     * 全部解析完被调用
                     * @param analysisContext
                     */
                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        log.info("readSheet1 全部解析完成");
                    }
                }).build();

        ReadSheet readSheet2 = EasyExcel.readSheet(1).head(DemoData2.class)
                .headRowNumber(2) // 注意Sheet2表头占了两行
                .registerReadListener(new AnalysisEventListener<DemoData2>() { //注册读的监听器
                    /**
                     * 每解析一行excel数据,就会被调用一次
                     * @param demoData2
                     * @param analysisContext
                     */
                    @Override
                    public void invoke(DemoData2 demoData2, AnalysisContext analysisContext) {
                        log.info("readSheet2 解析到一条数据为:{}", JSON.toJSONString(demoData2));
                    }

                    /**
                     * 全部解析完被调用
                     * @param analysisContext
                     */
                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        log.info("readSheet2 全部解析完成");
                    }
                }).build();

        excelReader.read(readSheet1, readSheet2);
    }

在这里插入图片描述

2、自定义读监听器

2.1 自定义读监听器

自定义一个通用的读监听器,继承 AnalysisEventListener类。

@Slf4j
public class CustomEasyExcelReadListener<T> extends AnalysisEventListener<T> {
    // 保存读取的对象
    private final List<T> rows = new ArrayList<>();

    // Sheet对应的名字
    private String sheetName = "";

    // 获取对应类
    private Class headClazz;

    // 此集合用来存储错误信息
    private final List<String> errorMessage = new ArrayList<>();

    public CustomEasyExcelReadListener(Class headClazz) {
        this.headClazz = headClazz;
    }


    /**
     * 通过Class获取类字段信息
     *
     * @param headClazz
     * @return
     * @throws NoSuchFieldException
     */
    public Map<Integer, String> getIndexNameMap(Class headClazz) throws NoSuchFieldException {
        Map<Integer, String> result = new HashMap<>();
        Field field;
        Field[] fields = headClazz.getDeclaredFields();     //获取类中所有的属性
        for (int i = 0; i < fields.length; i++) {
            field = headClazz.getDeclaredField(fields[i].getName());
            //log.info(String.valueOf(field));
            field.setAccessible(true);
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);//获取根据注解的方式获取ExcelProperty修饰的字段
            if (excelProperty != null) {
                int index = excelProperty.index();         //索引值
                String[] values = excelProperty.value();   //字段值
                StringBuilder value = new StringBuilder();
                for (String v : values) {
                    value.append(v);
                }
                result.put(index, value.toString());
            }
        }
        return result;
    }

    /**
     * 读取表头数据存在headMap中。如果你校验表头格式时可以使用。
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("解析到一条表头数据:{}", JSON.toJSONString(headMap));
        Map<Integer, String> head = new HashMap<>();
        try {
            //通过Class获取到使用@ExcelProperty注解配置的字段
            head = getIndexNameMap(headClazz);
            log.info(String.valueOf(head));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        //解析到的excel表头和实体配置的进行比对
        Set<Integer> keySet = head.keySet();
        for (Integer key : keySet) {
            if (StringUtils.isEmpty(headMap.get(key))) {
                errorMessage.add("您上传的文件第" + (key + 1) + "列表头为空,请按照模板检查后重新上传");
            }
            if (!headMap.get(key).equals(head.get(key))) {
                errorMessage.add("您上传的文件第" + (key + 1) + "列表头与模板表头不一致,请检查后重新上传");
            }
        }
    }

    /**
     * 读取一行一行数据到object
     *
     * @param object
     * @param context
     */
    @Override
    public void invoke(T object, AnalysisContext context) {
        // 实际数据量比较大时,rows里的数据可以存到一定量之后进行批量处理(比如存到数据库),
        // 然后清空列表,以防止内存占用过多造成OOM
        rows.add(object);
    }

    /**
     * 在完成数据解析后进行的操作。AOP思想。
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 当前sheet的名称 编码获取类似
        sheetName = context.readSheetHolder().getSheetName();
        log.info("sheetName = {} -> 所有数据解析完成, read {} rows", sheetName, rows.size());
    }

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception 抛出异常
     * @param context   解析内容
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            errorMessage.add("第" + excelDataConvertException.getRowIndex() + "行,第" + (excelDataConvertException.getColumnIndex() + 1) +
                    "列数据类型解析异常,数据为:" + excelDataConvertException.getCellData());
            log.error("第{}行,第{}列数据类型解析异常,数据为:{}", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex() + 1,
                    excelDataConvertException.getCellData());
        }
    }

    public List<T> getRows() {
        return rows;
    }

    public Class getHeadClazz() {
        return headClazz;
    }

    public List<String> getErrorMessage() {
        return errorMessage;
    }

    public String getSheetName() {
        return sheetName;
    }
}

2.2 实例测试

    /**
     * 自定义 ReadListener监听器测试方法
     */
    private static void manySheetReadWthCustomReadListener() {
        String fileName = "D:\\TempFiles\\表格.xlsx";
        ExcelReader excelReader = EasyExcel.read(fileName).build();

        CustomEasyExcelReadListener mySheet1Listener = new CustomEasyExcelReadListener(DemoData.class);
        CustomEasyExcelReadListener mySheet2Listener = new CustomEasyExcelReadListener(DemoData2.class);

        List<ReadSheet> readSheetList = new ArrayList<>();
        ReadSheet readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).headRowNumber(1)
                .registerReadListener(mySheet1Listener).build();

        ReadSheet readSheet2 = EasyExcel.readSheet(1).head(DemoData2.class)
                .headRowNumber(2) // 注意Sheet2表头占了两行
                .registerReadListener(mySheet2Listener).build();

        readSheetList.add(readSheet1);
        readSheetList.add(readSheet2);
        excelReader.read(readSheetList);

        System.out.println("============Sheet1 解析解析完成,数据如下================");
        //获取Sheet1监听器读到的数据,拿到的数据大家可以根据需求进行数据库操作
        List rows = mySheet1Listener.getRows();
        for (Object row : rows) {
            log.info("Sheet1 解析到一条数据为:{}", JSON.toJSONString(row));
        }
        //获取解决出的错误信息
        List<String> errorMessage = mySheet1Listener.getErrorMessage();
        log.info("Sheet1 解析错误信息为:{}", JSON.toJSONString(errorMessage));


        System.out.println("============Sheet2 解析解析完成,数据如下================");
        //获取Sheet2监听器读到的数据,拿到的数据大家可以根据需求进行数据库操作
        List<DemoData2> demoData2List = mySheet2Listener.getRows();
        for (DemoData2 demoData2 : demoData2List) {
            log.info("Sheet2 解析到一条数据为:{}", JSON.toJSONString(demoData2));
        }
        //获取解决出的错误信息
        List<String> errorMessage2 = mySheet2Listener.getErrorMessage();
        log.info("Sheet2 解析错误信息为:{}", JSON.toJSONString(errorMessage2));
    }

在这里插入图片描述

通用的自定义读监听器中 invokeHeadMap方法校验有点不合适,大家可以创建针对性的读监听器,分别处理。一般 invokeHeadMap方法根据需要使用。

更多操作查看官方文档:https://easyexcel.opensource.alibaba.com/

– 求知若饥,虚心若愚。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用 CSDN 开发的 EasyExcel 库来取 Excel 文件。首先,确保你已经导入了 EasyExcel 的相关依赖。然后,使用以下代码取 Excel 文件: ```java import com.alibaba.excel.EasyExcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; public class ExcelReader { public static void main(String[] args) { String fileName = "path/to/your/file.xlsx"; // 替换为你的文件路径 // 使用 EasyExcel 读取文件 EasyExcel.read(fileName, YourDataClass.class, new ExcelListener()).sheet().doRead(); } public static class YourDataClass { // 定义与 Excel 文件中列对应的属性 private String column1; private Integer column2; // ... // 添加属性的 getter 和 setter 方法 // 重写 toString() 方法(可选) @Override public String toString() { return "YourDataClass{" + "column1='" + column1 + '\'' + ", column2=" + column2 + // ... '}'; } } public static class ExcelListener extends AnalysisEventListener<YourDataClass> { @Override public void invoke(YourDataClass data, AnalysisContext context) { // 每取一行数据,都会调用该方法进行处理 System.out.println("Read data: " + data.toString()); // 在这里可以对数据进行进一步处理 } @Override public void doAfterAllAnalysed(AnalysisContext context) { // 数据取完成后的处理逻辑 } } } ``` 以上代码中,你需要将 `YourDataClass` 替换为你的数据类,并根据你的 Excel 文件的列结构定义类的属性。在 `invoke()` 方法中,你可以对每一行数据进行处理,例如打印或保存到数据库中。 记得将 `fileName` 替换为你的 Excel 文件的路径,然后运行该代码即可取 Excel 文件。希望能帮到你!如有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值