• EasyExcel 学习笔记


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

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试数据

    测试的 excel 放在此处:

    private String filePath = "E:\\temp\\测试数据.xlsx";
    
    • 1

    实体类

    用法说明
    @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    自定义转换器

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    最简单的读

    写法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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    写法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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    多行头(跳过N行)

    如果头有多行,那么可以指定跳过多少行,开始读取。
    只要加了一个 headRowNumber 用于说明头有多少行。

    @Test
    public void headRead() {
        EasyExcel.read(filePath, new NoModelDataListener()).sheet().headRowNumber(7).doRead();
    }
    
    • 1
    • 2
    • 3
    • 4

    读多个sheet

    节约篇幅我这里直接用了 PageReadListener
    这里需要注意:每个sheet读取完毕后调用一次doAfterAllAnalysed
    因为这里只给了一个监听器,最后所有sheet都会写进同一个监听器里。(直观的效果就是如下代码每次输出都包含上一次的内容。人个理解是要处理数据的话,在读完最后一个sheet时再一起处理就好了。)

    读全部 sheet

    EasyExcel.read(fileName, DemoData.class, new PageReadListener<>(dataList -> {
        dataList.forEach(demoData -> log.info("读取到一条数据{}", JSON.toJSONString(demoData)));
    })).doReadAll();
    
    • 1
    • 2
    • 3

    读部分 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    同步的返回

    最简单的读的区别只是把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)));
    
    • 1
    • 2

    读取表头数据

    懒得从头实现一个监听器,直接用 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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    额外信息(批注、超链接、合并单元格信息读取)

    官网示例代码 中按类型分别判断并处理有点长,我这直接输出就好了。
    同样偷个懒重写 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();// 读取合并单元格信息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    读取公式和单元格类型

    关键在于实体中的字体用 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; // 不一定能完美的获取,等官方后续
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    List<CellDataReadDemoData> objects = EasyExcel.read(fileName).head(CellDataReadDemoData.class).sheet().doReadSync();
    objects.forEach(demoData -> log.info("解析到一条数据{}", JSON.toJSONString(demoData)));
    
    • 1
    • 2
    • 这里表的内容
      在这里插入图片描述

    • 这是一行的效果

    {
    	"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
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    数据转换等异常处理

    @Data
    @EqualsAndHashCode
    public class ExceptionDemoData {
        private Date date; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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("正常结束");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    官方说的是在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。但我还是被终断了执行,不知道是不是我理解的不对。
    在这里插入图片描述

    不创建对象的读

    正常创建按表字段,创建实体,然后读数据,没什么好说的。这里看一下:不创建对象的读

    监听器

    就是官方代码,只是补充了一下注释。

    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("存储数据库成功!");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    测试代码

    这注释也是官方的,好像少字了。

        @Test
        public void noModelRead() {
            // 这里 只要,然后读取第一个sheet 同步读取会自动finish
            EasyExcel.read(filePath, new NoModelDataListener()).sheet().doRead();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    web中的读

    从上传的 Excel 中读取数据到 List<DemoData> 并返回。用到了同步读取doReadSync()

    @Slf4j
    @RestController
    public class ExcelControllor {
        @PostMapping("upload")
        public List<DemoData> importExcel(MultipartFile file) throws IOException {
            return EasyExcel.read(file.getInputStream(), DemoData.class, new PageReadListener<DemoData>(dataList -> {
                dataList.forEach(demoData -> log.info("读取到一条数据{}", JSON.toJSONString(demoData)));
            })).sheet().doReadSync();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    无实体版本

    @PostMapping("upload")
    public HashMap<String, Object> importExcel(MultipartFile file) throws IOException {
        return new HashMap<String, Object>(){{ 
            put("code", 200);
            put("data", EasyExcel.read(file.getInputStream()).sheet().doReadSync());
        }};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    参考资料

    Easy Excel 官网
    Easy Excel 官方文档:快速开始 (常规操作这里都有,照做就行了)
    Easy Excel 官方文档:读excel-通用参数

    🏆【Alibaba中间件技术系列】「EasyExcel实战案例」实战研究一下EasyExcel如何从指定文件位置进行读取数据
    🏆【Alibaba 工具型技术系列】「EasyExcel 技术专题」实战技术针对于项目中常用的 Excel 操作指南
    🏆【Alibaba 工具型技术系列】「EasyExcel 技术专题」摒除 OOM!让你的 Excel 操作变得更加优雅和安全

  • 相关阅读:
    设0<c<1,a1=c/2,a(n+1)=c/2+an²/2,证明数列an收敛,并求其极限
    react 也就这么回事 01 —— React 元素的创建和渲染
    Flink-cdc 同步mysql数据
    正则表达式
    【云原生】Kubernetes----Ingress对外服务
    idea插件下载后删除不掉或者在Setting中看不到---去插件位置中删除掉,但是在AppData文件夹内删除掉
    Node.js中的child_process模块的作用
    计算机网络-谢希仁-第7版 第6章 应用层
    数据栅格化
    uni小程序图片预览
  • 原文地址:https://blog.csdn.net/jx520/article/details/125479375