【EasyExcel】入门学习及快速上手实践复盘

本文介绍了如何使用EasyExcel进行Excel数据的读取,包括定义数据结构、使用监听器处理数据,特别是针对动态表头的情况。文中提到了两种解决方案来限制读取行数,一种是通过抛出异常中断,另一种是记录总行数。此外,还展示了如何在读取过程中进行数据处理并追加写入到新文件中。
摘要由CSDN通过智能技术生成

官方文档:https://easyexcel.opensource.alibaba.com/docs/current/

从Excel读入数据

最简单的读在这里不再赘述,参考文章最上方的官方文档(全中文的,里面已经写得很通俗易懂了)。这里只大致讲一下主要步骤:
1.定义接收数据的对象结构;
2.定义处理数据的监听器,其中包含每批数据解析完的清理机制和储存机制。拿官方文档的demo来举例:

  	/**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
 	/**
     * 缓存的数据
     */
    private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    
 	@Override
    public void invoke(DemoData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

监听器里定义了BATCH_COUNT,每行解析出来的数据都会放入cachedDataList 中,cachedDataList 达到BATCH_COUNT会先存储这部分数据,然后将cachedDataList清掉。这样保证读是一批一批落库的,不会一下读大量数据OOM。

动态读

编码过程中遇到的实际情况是用户输入的excel是动态表头,我们无法使用特定的对象去接收数据,只能用最基础的Map去接收。参考官方文档中不创建对象的读。

private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

用来接收数据的结构是List<Map<Integer, String>>,其中Map<Integer, String>>是每一行的数据,map的key是列序号,从0开始,value是此行此列单元格的值。

只解析部分数据

解决方案1:
参考了https://blog.csdn.net/u012751272/article/details/126298071的解决方案,感谢大佬。
思路就是解析完需要的数据后用抛出异常来中断后续解析流程,外层捕获异常即可,不影响解析功能,且能大幅提高性能。比如需求只需解析出20行示例数据,程序只需解析完这20行即可返回,无需等待整个表所有数据解析完。

解决方案2:
如果需求要求的是解析示例数据同时返回用户输入的excel的总行数,那就不能采用抛出异常中断流程的方法。代码(已脱敏)如下:

public class ParsePartExcelListener extends AnalysisEventListener<Map<Integer, String>> {

    private List<Map<Integer, String>> dataList = new ArrayList<>();
    private List<Map<Integer, String>> headList = new ArrayList<>();
    //需要解析的行数
    private Integer limitSize;
    //总行数
    private Integer rowCount = 0;

    private int BATCH_SIZE = 100;


    public ParsePartExcelListener(int limitSize) {
        this.limitSize = limitSize;
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        headList.add(headMap);
    }

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext analysisContext) {
        if (!(Objects.nonNull(limitSize) && rowCount >= limitSize)) {
            dataList.add(data);
        }
        rowCount++;

        // 达到BATCH_COUNT了,需要去清一次数据,防止数据几万条数据在内存,容易OOM
        if (dataList.size() >= BATCH_SIZE) {

            // 存储完成清理 list
            dataList.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("所有数据解析完成!");
    }
 }

每批解析出的数据复杂处理

和demo中的存储逻辑一样,在解析完一批次数据之后,统一对这一批数据进行处理,然后清除。

public class ParseExcelListener extends AnalysisEventListener<Map<Integer, String>> {

    private Map<Integer, Map<Integer, String>> dataMap = Maps.newHashMap();
    private List<Map<Integer, String>> headList = new ArrayList<>();
    private ExcelDataHandler<Map<Integer, String>> dataHandler;
    private int rowId = 1;


    public ParseExcelListener(ExcelDataHandler<Map<Integer, String>> dataHandler) {
        this.dataHandler = dataHandler;
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        headList.add(headMap);
    }

    @Override
    public void invoke(Map<Integer, String> integerStringMap, AnalysisContext analysisContext) {
        dataMap.put(rowId, integerStringMap);
        rowId++;

        // 达到BATCH_COUNT了,需要去清一次数据,防止数据几万条数据在内存,容易OOM
        if (dataMap.size() >= dataHandler.getHandlerBatch()) {

            dataHandler.actuator(dataMap, headList);
            // 存储完成清理 list
            dataMap.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("所有数据解析完成!");
        dataHandler.actuator(dataMap, headList);
    }

}

 		ParseExcelListener parseExcelListener = new ParseExcelListener(new ExcelDataHandler<Map<Integer, String>>() {
            /**
             * @Param: Map<行号, Map < 列号, 单元格内容>>
             */
            @Override
            public void actuator(Map<Integer, Map<Integer, String>> dataMap, List<Map<Integer, String>> headList) {
               //数据处理
            }
        });
        try {
            EasyExcelFactory.read(inputStream).registerReadListener(parseExcelListener).headRowNumber(1).sheet().doRead();
        } catch (Exception e) {
            log.warn("解析数据异常,文件url:{},错误原因:{}", importContext.getImportsRecord().getOriginUrl(), e);
            throw e;
        }

边读边写,如何追加写入

读是分批读,每一批有一个批量处理数据的方法,可以在批量处理数据时将读取到的数据处理后写入新文件中。
先看demo:

    /**
     * 重复多次写入
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
     * <p>
     * 2. 使用{@link ExcelProperty}注解指定复杂的头
     * <p>
     * 3. 直接调用二次写入即可
     */
    @Test
    public void repeatedWrite() {
        // 方法1: 如果写到同一个sheet
        String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            // 这里注意 如果同一个sheet只要创建一次
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
            for (int i = 0; i < 5; i++) {
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<DemoData> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

        // 方法2: 如果写到不同的sheet 同一个对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<DemoData> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

        // 方法3 如果写到不同的sheet 不同的对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class
                // 实际上可以一直变
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<DemoData> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

    }

        //本地新建一个临时文件
        File file = FileUtil.create(filePath, fileName);
        ExcelWriter excelWriter = EasyExcelFactory.write(file).build();
        WriteSheet writeSheet = EasyExcelFactory.writerSheet().build();
       
        ParseExcelListener parseExcelListener = new ParseExcelListener(new ExcelDataHandler<Map<Integer, String>>() {
            /**
             * @Param: Map<行号, Map < 列号, 单元格内容>>
             */
            @Override
            public void actuator(Map<Integer, Map<Integer, String>> dataMap, List<Map<Integer, String>> headList) {
            	//每批数据统一处理
            	//判断临时文件是否已存在,已存在则不需要表头,直接往后面追加写
            	if (!file.exists() || file.length() == 0) {
            		writeSheet.setHead(getHeader(importContext.getMapping(), excelHeadIdxNameMap));
        		}
        		excelWriter.write(importSafetyInventoryResults, writeSheet);
            }
        });
        try {
            EasyExcelFactory.read(inputStream).registerReadListener(parseExcelListener).headRowNumber(1).sheet().doRead();
        } catch (Exception e) {
            log.warn("解析数据异常,文件url:{},错误原因:{}", importContext.getImportsRecord().getOriginUrl(), e);
            throw e;
        } finally {
        	//所有数据解析完再关闭excelWriter,否则每批数据都会覆盖前面的内容
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
		//处理完需要把本地的临时文件删掉
        FileUtil.clearFile(file);

鸣谢

https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read#%E4%B8%8D%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AF%BB
https://blog.csdn.net/u012751272/article/details/126298071

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
EasyExcel 是一个基于阿里巴巴的开源项目 easyexcel 开发的 Excel 工具类库,可以方便快捷地读取、写入 Excel 文件。它支持大批量数据的导入导出,可以读取 Excel 中的复杂表格,并且能够将 Java 对象映射到 Excel 单元格中。 在 EasyExcel 中,图片的导入和导出可以通过一个 POJO 类来实现。下面是一个示例: ```java public class ImageData { //图片文件名 private String fileName; //图片字节数组 private byte[] imageBytes; public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public byte[] getImageBytes() { return imageBytes; } public void setImageBytes(byte[] imageBytes) { this.imageBytes = imageBytes; } } ``` 这个 POJO 类包含了图片文件名和图片字节数组两个属性,分别用于表示图片的文件名和内容。在导入和导出 Excel 文件时,我们可以使用 EasyExcel 提供的注解来指定这个 POJO 类中的属性和 Excel 表格中的列之间的映射关系。 下面是一个示例,演示了如何将 POJO 类中的图片数据导出到 Excel 文件中: ```java public static void writeExcel() throws IOException { //定义 Excel 文件输出流 OutputStream outputStream = new FileOutputStream("test.xlsx"); //创建 EasyExcel 写入器 ExcelWriter excelWriter = EasyExcel.write(outputStream, ImageData.class).build(); //创建一个 Sheet WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build(); //创建一个图片 BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, 100, 100); g.setColor(Color.BLACK); g.drawString("Hello, EasyExcel", 10, 50); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(image, "png", outputStream); byte[] bytes = outputStream.toByteArray(); //创建一个 ImageData 对象 ImageData imageData = new ImageData(); imageData.setFileName("test.png"); imageData.setImageBytes(bytes); //将 ImageData 对象写入到 Excel 文件中 excelWriter.write(Arrays.asList(imageData), writeSheet); //关闭 Excel 写入器 excelWriter.finish(); } ``` 在这个示例中,我们首先创建了一个 BufferedImage 对象,然后使用 Graphics2D 绘制了一个字符串,并将其转换为了字节数组。接着,我们创建了一个 ImageData 对象,并将图片文件名和字节数组分别设置到了对象的属性中。最后,我们将 ImageData 对象写入到了 Excel 文件中。 导入 Excel 文件中包含图片数据的过程与导出类似,只需要使用 EasyExcel 提供的读取器和监听器,读取 Excel 文件中的数据,并将读取到的数据转换为 POJO 对象即可。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值