EasyExcel详解(结合官方文档)

news2025/2/28 6:40:27

EasyExcel

零、前言

文章是根据官方文档,加上自己的测试运行总结出来的,目前只总结的EasyExcel读的部分,写的部分还未完结,后续会更新

1、官方文档

https://easyexcel.opensource.alibaba.com/

2、EasyExcel的maven依赖

<!--Easy Excel-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.7</version>
</dependency>

一、具体使用演示&说明

1、读Excel

1.1 简单的读

创建一个读取对象,存储读取的内容

/**
 * 读对象
 * @author banana
 * @create 2023-12-26 11:39
 */

@Data  //生成getter、setter、toString、equals、hashCode等方法
public class ReadDemoData {
    
    private String string;

    private Date date;

    private Double doubleData;
}

创建一个读的监听器

package com.example.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson2.JSON;
import com.example.model.pojo.ReadDemoData;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;

/**
 *
 * @author banana
 * @create 2023-12-26 11:50
 */

@Slf4j //Lombok 注解之一,它可以帮助我们在 Java 类中自动添加日志记录功能
public class ReadDemoDataListener implements ReadListener<ReadDemoData> {

    //每隔100条存储数据库,然后清理list,方便内存的回收
    private static final int BATCH_COUNT = 100;

    //缓存数据
    private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    //在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {

    }

    //在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {

    }

    //在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
    @Override
    public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
        cachedDataList.add(readDemoData);
        //达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
        //目的:防止几万条数据在内存中,容易OOM
        if(cachedDataList.size() >= BATCH_COUNT)
        {
            //一些业务操作(如存储数据库)
            //……
            saveData();

            //清除缓存
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    //在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    //在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //一些业务操作(如存储数据库,简单打印剩余数据)
        cachedDataList.stream().forEach(System.out::println);

        log.info("所有数据解析完成!");
    }

    //判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
    // 如果返回 false,则结束读取数据的过程。
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return false;
    }

    //模拟数据存储(这里就是简单的打印一下)
    private void saveData(){
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        cachedDataList.stream().forEach(System.out::println);
        log.info("存储数据库成功!");
    }
}

由于我们之后的测试都要在单元测试中进行,因此我们在单元测试目录下创建一个resources目录,用于后面对excel文件的读取

创建一个excel文件,名称为readExcel

在这里插入图片描述

excel文件中共有四百十条数据
在这里插入图片描述

在单元测试中创建一个方法,专门根据名称去读取单元测试目录中resources目录下保存的excel文件

//根据文件名称,获取Excel目录地址
private String getExcelUrl(String docName){
    return this.getClass().getClassLoader().getResource("").getPath()  + docName;
}

之后创建一个单元测试类,去进行简单的读取操作

① 方法一:JDK8+ ,不用额外写一个DemoDataListener

实现代码:

//写法一
String fileName =  getExcelUrl("readExcel.xlsx");
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(fileName, ReadDemoData.class, new PageReadListener<ReadDemoData>(dataList ->{
    for(ReadDemoData demoData : dataList){
        log.info("读取到一条数据{}", JSON.toJSONString(demoData));
    }
})).sheet().doRead();

具体运行结果:

在这里插入图片描述

我们可以看下PageReadListener源码,其就是继承ReadListener接口的一个实现类,相当于是EasyExcel帮我们写好的读的监听器,我们直接拿来用就好了

public class PageReadListener<T> implements ReadListener<T> {
    public static int BATCH_COUNT = 100;
    private List<T> cachedDataList;
    private final Consumer<List<T>> consumer;
    private final int batchCount;

    public PageReadListener(Consumer<List<T>> consumer) {
        this(consumer, BATCH_COUNT);
    }

    public PageReadListener(Consumer<List<T>> consumer, int batchCount) {
        this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        this.consumer = consumer;
        this.batchCount = batchCount;
    }

    public void invoke(T data, AnalysisContext context) {
        this.cachedDataList.add(data);
        if (this.cachedDataList.size() >= this.batchCount) {
            this.consumer.accept(this.cachedDataList);
            this.cachedDataList = ListUtils.newArrayListWithExpectedSize(this.batchCount);
        }

    }

    public void doAfterAllAnalysed(AnalysisContext context) {
        if (CollectionUtils.isNotEmpty(this.cachedDataList)) {
            this.consumer.accept(this.cachedDataList);
        }

    }
}

并且其每次读取100条数据,然后返回过来,执行监听器中的方法。如果我们需要具体指定需要返回多少行,可以调用PageReadListener的构造器进行设置,如下所示:

调用函数PageReadListener构造器,指定batchCount的值:

public PageReadListener(Consumer<List<T>> consumer, int batchCount) {
        this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        this.consumer = consumer;
        this.batchCount = batchCount;
    }

完整调用构造器方式如下

//写法一
String fileName =  getExcelUrl("readExcel.xlsx");
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(fileName, ReadDemoData.class, new PageReadListener<ReadDemoData>(dataList ->{
    for(ReadDemoData demoData : dataList){
        log.info("读取到一条数据{}", JSON.toJSONString(demoData));
    }
}, 20)).sheet().doRead();


每次到达监听器时的数量(指定batchCount前):

在这里插入图片描述

每次到达监听器时的数量(指定batchCount后):

在这里插入图片描述

② 方法二:匿名内部类 不用额外写一个DemoDataListener

其实质上是和方法一一样,只不过将监听器写成了内部类的方式去实现

代码实现:

String fileName = getExcelUrl("readExcel.xlsx");
EasyExcel.read(fileName, ReadDemoData.class, new ReadListener<ReadDemoData>() {
    //单次缓存数据量
    public static final int BATCH_COUNT = 100;

    //临时存储
    private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    @Override
    public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
        cachedDataList.add(readDemoData);
        if(cachedDataList.size() >= BATCH_COUNT){
            saveData();
            //存储完成,清理list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        saveData();
    }

    //模拟数据存储(这里就是简单的打印一下)
    private void saveData(){
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        cachedDataList.stream().forEach(System.out::println);
        log.info("存储数据库成功!");
    }
}).sheet().doRead();

        

运行结果:

在这里插入图片描述

③ 方式三:使用自定义的ReadListener的实现类

代码实现:

//写法三
String fileName = getExcelUrl("readExcel.xlsx");
//有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, ReadDemoData.class, new ReadDemoDataListener()).sheet().doRead();

ReadDemoDataListener

@Slf4j //Lombok 注解之一,它可以帮助我们在 Java 类中自动添加日志记录功能
public class ReadDemoDataListener implements ReadListener<ReadDemoData> {

    //每隔100条存储数据库,然后清理list,方便内存的回收
    private static final int BATCH_COUNT = 100;

    //缓存数据
    private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    //在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {

    }

    //在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {

    }

    //在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
    @Override
    public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
        cachedDataList.add(readDemoData);
        //达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
        //目的:防止几万条数据在内存中,容易OOM
        if(cachedDataList.size() >= BATCH_COUNT)
        {
            //一些业务操作(如存储数据库)
            //……
            saveData();

            //清除缓存
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    //在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    //在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //一些业务操作(如存储数据库,简单打印剩余数据)
        cachedDataList.stream().forEach(System.out::println);

        log.info("所有数据解析完成!");
    }

    //判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
    // 如果返回 false,则结束读取数据的过程。
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }

    //模拟数据存储(这里就是简单的打印一下)
    private void saveData(){
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        cachedDataList.stream().forEach(System.out::println);
        log.info("存储数据库成功!");
    }
}

运行结果:

在这里插入图片描述

④ 方法四:使用 build 方法构建 ExcelReader 对象

使用了try-with-resources 结构,通过使用 try-with-resources 结构,可以确保在代码块执行完毕或者出现异常时,会自动关闭在 try 括号中声明的资源,下面的ExcelReader 对象属于一个需要手动关闭的资源,try-with-resources 可以确保在读取操作结束后会自动关闭 ExcelReader,释放相关资源,无需手动编写关闭资源的代码。

try-with-resources结构如下:

// ResourceType 是要操作的资源类型
// initialization 是初始化资源的表达式。
//在 try 块中使用资源的代码块执行完毕后,会自动调用资源的 close() 方法进行关闭
try (ResourceType resource = initialization) {
    // 使用资源的代码块
} catch (ExceptionType exception) {
    // 异常处理代码块
}

代码实现:

//写法四
String fileName = getExcelUrl("readExcel.xlsx");
// 一个文件一个reader
try (ExcelReader excelReader = EasyExcel.read(fileName, ReadDemoData.class, new ReadDemoDataListener()).build()) {
    // 构建一个sheet 这里可以指定名字或者no
    ReadSheet readSheet = EasyExcel.readSheet(0).build();
    // 读取一个sheet
    excelReader.read(readSheet);
}

运行结果:

在这里插入图片描述

1.2 指定列的下标或者列名

① 创建读对象:

/** 指定列的下标或者列名的读对象
 * @author banana
 * @create 2023-12-26 21:00
 */
@Data
public class IndexOrNameData {
    /**
     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
     */
    @ExcelProperty(index = 2)
    private Double doubleData;
    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @ExcelProperty("字符串标题")
    private String string;
    
    @ExcelProperty("日期标题")
    private Date date;
}

@ExcelProperty注解说明:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelProperty {
    
    //字符串数组类型,默认值为 ""。用于指定 Excel 中的列名,可以指定多个列名,用于匹配 Excel 文件中的列,用于读取或写入 Excel 数据时的映射关系
    String[] value() default {""};

    //整型变量,默认值为 -1。用于指定 Excel 中的列索引,用于读取或写入 Excel 数据时的映射关系
    int index() default -1;

    //整型变量,默认值为 2147483647。用于指定 Excel 中的列顺序,表示在 Excel 文件中的列的位置
    int order() default 2147483647;

    //泛型类型为 Converter 的 Class,默认值为 AutoConverter.class。用于指定转换器,用于在读取或写入 Excel 数据时进行数据类型的转换
    Class<? extends Converter<?>> converter() default AutoConverter.class;

    //原来用于指定格式化字符串,用于读取或写入 Excel 数据时的格式化(过时)
    /** @deprecated */
    @Deprecated
    String format() default "";
}

②创建一个监听器(使用1.1中的监听器即可)

/**
 * @author banana
 * @create 2023-12-26 21:14
 */
@Slf4j
public class IndexOrNameDataListener implements ReadListener<IndexOrNameData> {
    //每隔100条存储数据库,然后清理list,方便内存的回收
    private static final int BATCH_COUNT = 100;

    //缓存数据
    private List<IndexOrNameData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    //在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {

    }

    //在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {

    }

    //在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
    @Override
    public void invoke(IndexOrNameData indexOrNameData, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(indexOrNameData));
        cachedDataList.add(indexOrNameData);
        //达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
        //目的:防止几万条数据在内存中,容易OOM
        if(cachedDataList.size() >= BATCH_COUNT)
        {
            //一些业务操作(如存储数据库)
            //……
            saveData();

            //清除缓存
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    //在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    //在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //一些业务操作(如存储数据库,简单打印剩余数据)
        cachedDataList.stream().forEach(System.out::println);

        log.info("所有数据解析完成!");
    }

    //判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
    // 如果返回 false,则结束读取数据的过程。
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }

    //模拟数据存储(这里就是简单的打印一下)
    private void saveData(){
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        cachedDataList.stream().forEach(System.out::println);
        log.info("存储数据库成功!");
    }
}

③单元测试代码

/**
     * 指定列的下标或者列名
     *
     * <p>1. 创建excel对应的实体对象,并使用 ExcelProperty注解. 参照IndexOrNameData
     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照 IndexOrNameDataListener
     * <p>3. 直接读即可
*/
@Test
public void indexOrNameRead() {
    String fileName = getExcelUrl("readExcel.xlsx");
    // 这里默认读取第一个sheet
    EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}

 //根据文件名称,获取Excel目录地址
private String getExcelUrl(String docName){
    return this.getClass().getClassLoader().getResource("").getPath()  + docName;
}

④运行结果

在这里插入图片描述

1.3 读多个sheet

①创建Excel表

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

② 创建对象

这里使用1.1的对象ReadDemoData

@Data  //生成getter、setter、toString、equals、hashCode等方法
public class ReadDemoData {

    private String string;

    private Date date;

    private Double doubleData;
}

③创建监听器

使用1.1的监听器ReadDemoDataListener

/**
 *
 * @author banana
 * @create 2023-12-26 11:50
 */

@Slf4j //Lombok 注解之一,它可以帮助我们在 Java 类中自动添加日志记录功能
public class ReadDemoDataListener implements ReadListener<ReadDemoData> {

    //每隔100条存储数据库,然后清理list,方便内存的回收
    private static final int BATCH_COUNT = 100;

    //缓存数据
    private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    //在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {

    }

    //在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {

    }

    //在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
    @Override
    public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
        cachedDataList.add(readDemoData);
        //达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
        //目的:防止几万条数据在内存中,容易OOM
        if(cachedDataList.size() >= BATCH_COUNT)
        {
            //一些业务操作(如存储数据库)
            //……
            saveData();

            //清除缓存
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    //在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    //在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //一些业务操作(如存储数据库,简单打印剩余数据)
        cachedDataList.stream().forEach(System.out::println);

        log.info("所有数据解析完成!");
    }

    //判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
    // 如果返回 false,则结束读取数据的过程。
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }

    //模拟数据存储(这里就是简单的打印一下)
    private void saveData(){
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        cachedDataList.stream().forEach(System.out::println);
        log.info("存储数据库成功!");
    }
}

④单元测试

方法一:通过doReadAll读取全部sheet

/**
     * 3 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
     * <p>
     * 1. 创建excel对应的实体对象 参照ReadDemoData
     * <p>
     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照ReadDemoDataListener
     * <p>
     * 3. 直接读即可
     */
    @Test
    public void repeateRead(){

        //写法一:读取全部sheet
        //获取Excel表的路径
        String fileName = getExcelUrl("MultipleSheetExcel.xlsx");
        // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。
        // 然后所有sheet都会往同一个DemoDataListener里面写
        EasyExcel.read(fileName, ReadDemoData.class, new ReadDemoDataListener()).doReadAll();

    }

运行结果:

前面是通过监听器的invoke方法中的log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));

后面是通过模拟存储数据库中的saveData方法打印出来的

关于后面为什么把Sheet1和Sheet2的内容全部打印出来,是因为设置的BATCH_COUNT是100,没有清楚过缓存,所以执行saveData方法的时候都打印出来了

在这里插入图片描述

方法二:通过doReadAll读取全部sheet

/**
     * 3 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
     * <p>
     * 1. 创建excel对应的实体对象 参照ReadDemoData
     * <p>
     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照ReadDemoDataListener
     * <p>
     * 3. 直接读即可
     */
@Test
public void repeateRead(){
    //写法二:分开读取各个Sheet的信息
    String fileName = getExcelUrl("MultipleSheetExcel.xlsx");
    try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
        // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
        ReadSheet readSheet1 =
            EasyExcel.readSheet(0).head(ReadDemoData.class).registerReadListener(new ReadDemoDataListener()).build();
        ReadSheet readSheet2 =
            EasyExcel.readSheet(1).head(ReadDemoData.class).registerReadListener(new ReadDemoDataListener()).build();
        // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
        excelReader.read(readSheet1, readSheet2);
    }


}

运行结果:

在这里插入图片描述

1.4 日期、数字或者自定义格式转换

① 创建自定义类型转换器

package com.example.converter;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;

/** 自定义转换器
 * @author banana
 * @create 2023-12-26 23:15
 */
//将自定义转换器基础EasyExcel提供的转换接口Converter
public class CustomStringStringConverter implements Converter<String> {
    //指定该转换器支持的 Java 类型,这里指定为 String.class
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }

    //指定该转换器支持的 Excel 数据类型,这里指定为 CellDataTypeEnum.STRING,表示支持读取 Excel 中的字符串类型数据
    /*
    以下是 CellDataTypeEnum 中定义的所有单元格类型:
    - BOOL:布尔类型
    - ERROR:错误类型
    - FORMULA:公式类型
    - INLINE_STR:内联字符串类型
    - NUMBER:数字类型
    - STRING:字符串类型
    - DATE:日期类型
    - EMPTY:空类型
    */
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 将读取到的 Excel 数据转换为 Java 对象中的数据
     * 这里读的时候会调用
     *
     * @param context
     * @return
     */
    @Override
    public String convertToJavaData(ReadConverterContext<?> context) {
        return "自定义:" + context.getReadCellData().getStringValue();
    }

    /**
     * 用于将 Java 对象中的数据转换为写入 Excel 文件中的数据
     * 这里是写的时候会调用 不用管
     *
     * @return
     */
    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
        return new WriteCellData<>(context.getValue());
    }

}

② 创建对象

/** 自定义格式转换对象
 * @author banana
 * @create 2023-12-26 23:13
 */
@Data
public class ConverterData {
    //我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
    @ExcelProperty(converter = CustomStringStringConverter.class)
    private String string;

    //这里用string 去接日期才能格式化。我想接收年月日格式
    @DateTimeFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
    private String date;

    //我想接收百分比的数字
    @NumberFormat("#.##%")
    private String doubleData;
}

③ 自定义格式转换监听器

/**
 * 自定义格式转换监听器
 * @author banana
 * @create 2023-12-26 23:29
 */
@Slf4j
public class ConverterDataListener implements ReadListener<ConverterData> {
    //每隔100条存储数据库,然后清理list,方便内存的回收
    private static final int BATCH_COUNT = 100;

    //缓存数据
    private List<ConverterData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    //在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {

    }

    //在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {

    }

    //在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
    @Override
    public void invoke(ConverterData converterData, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(converterData));
        cachedDataList.add(converterData);
        //达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
        //目的:防止几万条数据在内存中,容易OOM
        if(cachedDataList.size() >= BATCH_COUNT)
        {
            //一些业务操作(如存储数据库)
            //……
            saveData();

            //清除缓存
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    //在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    //在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //一些业务操作(如存储数据库,简单打印剩余数据)
        cachedDataList.stream().forEach(System.out::println);

        log.info("所有数据解析完成!");
    }

    //判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
    // 如果返回 false,则结束读取数据的过程。
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }

    //模拟数据存储(这里就是简单的打印一下)
    private void saveData(){
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        cachedDataList.stream().forEach(System.out::println);
        log.info("存储数据库成功!");
    }
}

④设置excel单元格格式

右击单元格,选择设置单元格格式

在这里插入图片描述

将日期标题的第一个单元格内容格式改成日期

在这里插入图片描述

将数字标题中第一个单元格内容改成数值

在这里插入图片描述

⑤单元测试

 /**
     * 4、日期、数字或者自定义格式转换
     * <p>
     * 默认读的转换器DefaultConverterLoader、loadDefaultReadConverter()
     * <p>1. 创建excel对应的实体对象 参照ConverterData.里面可以使用注解DateTimeFormat、NumberFormat或者自定义注解
     * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照ConverterDataListener
     * <p>3. 直接读即可
     */
@Test
public void converterRead() {
    String fileName = getExcelUrl("readExcel.xlsx");
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet
    EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())
        // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
        // 如果就想单个字段使用请使用@ExcelProperty 指定converter
        // .registerConverter(new CustomStringStringConverter())
        // 读取sheet
        .sheet().doRead();
}

运行结果:

在这里插入图片描述

1.5 日期、数字或者自定义格式转换
1.6 日期、数字或者自定义格式转换
1.7 读取表头数据

①监听器中重写invokeHeadMap方法方法

/**
 *	读取表头数据监听器
 * @author banana
 * @create 2023-12-26 23:49
 */
@Slf4j
public class DemoHeadDataListener implements ReadListener<ReadDemoData> {
    //每隔100条存储数据库,然后清理list,方便内存的回收
    private static final int BATCH_COUNT = 100;

    //缓存数据
    private List<ReadDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);


    //在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {

    }

    //在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
        // 如果想转成成 Map<Integer,String>
        // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener
        // 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换
    }

    //在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
    @Override
    public void invoke(ReadDemoData readDemoData, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(readDemoData));
        cachedDataList.add(readDemoData);
        //达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
        //目的:防止几万条数据在内存中,容易OOM
        if(cachedDataList.size() >= BATCH_COUNT)
        {
            //一些业务操作(如存储数据库)
            //……
            saveData();

            //清除缓存
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    //在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    //在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //一些业务操作(如存储数据库,简单打印剩余数据)
        cachedDataList.stream().forEach(System.out::println);

        log.info("所有数据解析完成!");
    }

    //判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
    // 如果返回 false,则结束读取数据的过程。
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }

    //模拟数据存储(这里就是简单的打印一下)
    private void saveData(){
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        cachedDataList.stream().forEach(System.out::println);
        log.info("存储数据库成功!");
    }
}

②单元测试

 /**
     * 7、读取表头数据
     *
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
     * <p>
     * 3. 直接读即可
     */
@Test
public void headerRead() {
    String fileName = getExcelUrl("readExcel.xlsx");
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet
    EasyExcel.read(fileName, ReadDemoData.class, new DemoHeadDataListener()).sheet().doRead();
}

运行结果:

在这里插入图片描述

2、写Excel

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1346962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot】常用注解

RequestBody&#xff1a;自动将请求体中的 json 数据转换为实体类对象。 PutMapping("/update")public Result update(RequestBody Validated User user) {userService.update(user);return Result.success();}

OPenGL GLSL

shji 数据类型 整型&#xff08;有符号/无符号&#xff09; 浮点数&#xff08;单精度&#xff09; 布尔值 向量类型/矩阵类型 bool bDone false int value 1; unint vale 21u float value 2.1 向量/分量类型 vec2,vec3,vec4 2分量 3 分量 4 分量复电向量 i…

CDH 6.3.2集成flink 1.18 zookeeper版本不匹配Flink-yarn启动失败

CDH 6.3.2集成flink 1.18 zookeeper版本不匹配Flink-yarn不能正常启动&#xff0c;而在CHD Web页面&#xff0c;flink日志报错提示不明确&#xff0c;不能定位具体错误。CM WEB启动失败错误日志如下图所示&#xff1a; CM查看完成错误日志 [31/Dec/2023 10:45:09 0000] 26000…

同义词替换降低论文相似度的注意事项 papergpt

大家好&#xff0c;今天来聊聊同义词替换降低论文相似度的注意事项&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;同义词替换降低论文相似度的注意事项 …

git分支场景操作,应用场景

文章目录 git分支操作1.git branch--目前处在的分支上2.git checkout--要切换的分支名字3.git merge--要合并的分支名字4.git branch -d--要删除的分支名字 git分支操作 假设目前我们目前有三个版本 1.git branch–目前处在的分支上 现在要开发一个新功能在新的分支上 新建一…

\r\n和缓冲区/进度条小程序

一 前置知识 带有\n就会立马刷新缓冲区(因为显示器是行刷新)&#xff0c;\r不会刷新缓冲区 刷新的2个场景: 1 ~fflush 缓冲区中存在\r或\n --> \r fflush --> 不换行的\n) 2 ~ 文件关闭自动刷新缓冲区 倒计时小程序0-9 %-d是左对齐,%d是右对齐 倒计时小程序0-99 …

阿贝云免费云服务器

最近体验了一下阿贝云的免费云服务器&#xff0c;总体感受是简单易上手。感兴趣的小伙伴们可以赶紧注册体验一下。 阿贝云官网&#xff1a; https://www.abeiyun.com 下图是我亲测的免费云服务器管理界面&#xff0c;免费云服务器的配置信是1核1GB&#xff0c;硬盘10GB&#x…

Goodbye2023, Hello 2024!

2023的所有比赛结束了&#xff0c;以后 xcpc 相关的比赛应该都和我没啥关系了&#xff0c;可能只打打蓝桥天梯了&#xff0c;等到明年的时候估计很多算法的东西也都忘记了吧&#xff0c;彻底退休了。打铁人不配叫退役&#xff0c;也不敢公开这篇文章&#xff0c;只敢在没人看的…

不擅长设计也能做好邮件营销:实用技巧与指南分享

电子邮件营销是跨境电商中非常常见的品牌推广方式之一。但对于那些不擅长设计的人来讲&#xff0c;设计一封引人注目的电子邮件可能是一个磨练。本文将分享一些提议&#xff0c;协助跨境电商顾客处理不擅长设计问题。 不擅长设计的情况下怎么进行邮件营销&#xff1f;首先我们…

mount -a 出错任然重启问题

问题来源 在磁盘分区挂载过后&#xff0c;为了创建的新分区的能够永久挂载&#xff0c;我们常常会在/etc/fstab下写下配置文件&#xff0c;使其永久挂载。但是该配置一旦写错&#xff0c;就面临这死机问题&#xff0c;为此&#xff0c;以下操作针对该问题进行 解决方案&#x…

【算法】数论---约数

约数里面的一个重要性质&#xff1a;一个数的约数都是成对存在的(以sqrt(x)为分界线) 一、求一个数的所有约数---试除法 int x; cin>>x; int yue[10000]{0},idx0; for(int i1;i<x/i;i) {if(x%i0){yue[idx]i;cout<<i<<" ";} }for(int iidx-1;i&…

ssm基于vue框架的点餐系统的设计与实现+vue论文

基于vue框架的点餐系统的设计与实现 摘要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。传统的点餐信息管理模式&#xff0c;采用人工登记的方式保存相关数据&#xff0c;这种以人力为主的…

SpringCloud 和 Linux 八股文第三期五问五答

SpringCloud 和 Linux 八股文第三期五问五答 作者&#xff1a;程序员小白条&#xff0c;个人博客 相信看了本文后&#xff0c;对你的面试是有一定帮助的&#xff01; ⭐点赞⭐收藏⭐不迷路&#xff01;⭐ 1&#xff09;Linux常用命令 2&#xff09;如何查看测试项目的日志 一…

4~20mA恒流源 --PLC自控控制

输出部分不接地 1.1&#xff0c; 常规恒流源的方式 用采样电阻 * 电流 控制电压的方式。 负载电阻 * 电流 < 工作电压 1.2&#xff0c;根据运放高阻的特性 Ir Ui/ R, Ir IL, 最大输出电流限制于 RL * Il < Ui. 输出部分接地&#xff0c;工程上更多是用于豪兰德恒流源…

深入探索小红书笔记详情API:解锁内容创新的无尽潜力

一、引言 在当今信息爆炸的时代&#xff0c;内容创新已经成为品牌和个人脱颖而出的关键。小红书&#xff0c;作为全球最大的消费类口碑库之一&#xff0c;每天产生大量的用户生成内容。而小红书笔记详情API&#xff0c;作为一个强大的工具&#xff0c;能够为内容创作者提供深入…

学习记录——BiFormer

BiFormer Vision Transformer with Bi-Level Routing Attention BiFormer:具有双电平路由注意的视觉变压器 摘要作为视觉转换器的核心组成部分,注意力是捕捉长期依赖关系的有力工具。然而,这样的能力是有代价的:当计算跨所有空间位置的成对令牌交互时,它会产生巨大的计算负…

通过Vue自定义指令实现前端埋点

在营销活动中&#xff0c;通过埋点可以获取用户的喜好及交互习惯&#xff0c;从而优化流程&#xff0c;进一步提升用户体验&#xff0c;提高转化率。 在之前的埋点方案实现中&#xff0c;都是在具体的按钮或者图片被点击或者被曝光时主动通过事件去上报埋点。这种方法在项目中…

[MySQL] MySQL中的事物

本片文章对MySQL中的事物进行了详解。其中包含了事物的特性、为什么要有事物、查看事物版本支持、事物常见操作、事物的隔离界别等等内容进行详细举例解释。同时还深入讲解了事物的隔离性&#xff0c;模拟实现MVCC多版本并发控制&#xff0c;也讲解了RR和RC的本质区别。希望本篇…

【Java开发岗面试】八股文—操作系统

声明&#xff1a; 背景&#xff1a;本人为24届双非硕校招生&#xff0c;已经完整经历了一次秋招&#xff0c;拿到了三个offer。本专题旨在分享自己的一些Java开发岗面试经验&#xff08;主要是校招&#xff09;&#xff0c;包括我自己总结的八股文、算法、项目、HR面和面试技巧…

2024新年快乐

前言 2023年马上就要过去&#xff0c;希望2024年会越来越好&#xff0c;根据自己学的内容来实现一些我的idea。 单片机 思路 最开始的构思是这样的&#xff0c;“2024”用数码管来输出然后中文的“新年快乐”用点阵来实现&#xff0c;但是点阵是动态的截图不好看&#xff0…