一文教你快速上手EasyExcel

原创 一安 一安未来 2022-08-12 08:41 发表于北京

收录于合集#干货分享集78个

大家好,我是一安,今天聊一下日常开发经常用到的文件导入导出功能,本篇采用阿里巴巴开源EasyExcel实现

前言

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。

EasyExcel采用一行一行的解析模式,从磁盘上一行行读取数据,逐个解析,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。

github地址: https://github.com/alibaba/easyexcel

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

示例代码

引入easyexcel,为了简便代码,一并引入了lombok

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
   </dependency>
   <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.6</version>
    </dependency>
    <!--EasyExcel相关依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>3.0.5</version>
    </dependency>

读文件

注意:方便测试,暂未使用spring管理,统一用main方法测试验证

文件监听器:

package com.capitek.excel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
    有个很重要的点 ReadExcelListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
*/
public class ReadExcelListener extends AnalysisEventListener<Map<Integer, String>> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ReadExcelListener.class);

    private DemoDAO demoDAO;
    public ReadExcelListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

     /** 这里会一行行的返回头
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }
    /**
     * 这个每一条数据解析都会来调用
     * @param data
     * @param context
     */
    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }
    /**
     * 所有数据解析完成了都会来调用
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", cachedDataList.size());
        demoDAO.save(cachedDataList);
        LOGGER.info("存储数据库成功!");
    }

}

持久层:

package com.capitek.excel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;
/**
 * 这个类让spring管理,小编偷个懒未使用。
 **/
public class DemoDAO {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDAO.class);
    public void save(List<Map<Integer, String>> cachedDataList) {
        LOGGER.info("入库条数:"+cachedDataList.size());
    }
}

测试验证:

注意:读的时候可以通过设置excelType(ExcelTypeEnum.XLSX)指定写入文件类型,在easyexcel 3.0以后支持写入csv

package com.capitek.excel;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.IoUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestReadMain {

    /**
     * @param stream           excel文件流
     * @param parseRowNumber   指定读取行
     */
    public static void parseExcelToView(byte[] stream, Integer parseRowNumber) {
        DemoDAO demoDAO = new DemoDAO();
        ReadExcelListener readListener = new ReadExcelListener(demoDAO);
        EasyExcelFactory.read(new ByteArrayInputStream(stream)).excelType(ExcelTypeEnum.XLSX).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet(0).doRead();
    }

    /**
     * 文件导入测试
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        FileInputStream inputStream = new FileInputStream(new File("easyexcel-export-user.xlsx"));
        byte[] stream = IoUtils.toByteArray(inputStream);
        parseExcelToView(stream, 2);
        inputStream.close();
    }

}

日志输出:

16:25:05.492 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条头数据:{0:"班级",1:"学生信息",2:"学生信息"}
16:25:05.496 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条头数据:{0:"班级",1:"姓名",2:"性别"}
16:25:05.506 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三0",2:"男"}
16:25:05.507 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三1",2:"男"}
16:25:05.513 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三2",2:"男"}
16:25:05.514 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三3",2:"男"}
16:25:05.515 [main] INFO com.capitek.excel.ReadExcelListener - 解析到一条数据:{0:"一年级",1:"张三4",2:"男"}
16:25:05.516 [main] INFO com.capitek.excel.ReadExcelListener - 5条数据,开始存储数据库!
16:25:05.516 [main] INFO com.capitek.excel.DemoDAO - 入库条数:5
16:25:05.516 [main] INFO com.capitek.excel.ReadExcelListener - 存储数据库成功!
16:25:05.516 [main] INFO com.capitek.excel.ReadExcelListener - 所有数据解析完成!

写文件

注意:写的时候可以通过设置excelType(ExcelTypeEnum.XLSX)指定写入文件类型,在easyexcel 3.0以后支持写入csv

package com.capitek.excel;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.fastjson.JSONArray;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;

public class TestWriteMain {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestWriteMain.class);



    /**
     * 动态导出文件(通过map方式计算)
     * @param headColumnMap  有序列头部
     * @param dataList       数据体
     * @return
     */
    public static byte[] exportExcelFile(LinkedHashMap<String, String> headColumnMap, List<Map<String, Object>> dataList){
        //获取列名称
        List<List<String>> excelHead = new ArrayList<>();
        if(MapUtils.isNotEmpty(headColumnMap)){
            //key为匹配符,value为列名,如果多级列名用逗号隔开
            headColumnMap.entrySet().forEach(entry -> {
                excelHead.add(CollectionUtil.newArrayList(entry.getValue().split(",")));
            });
        }
        List<List<Object>> excelRows = new ArrayList<>();
        if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){
            for (Map<String, Object> dataMap : dataList) {
                List<Object> rows = new ArrayList<>();
                headColumnMap.entrySet().forEach(headColumnEntry -> {
                    if(dataMap.containsKey(headColumnEntry.getKey())){
                        Object data = dataMap.get(headColumnEntry.getKey());
                        rows.add(data);
                    }
                });
                excelRows.add(rows);
            }
        }
        byte[] stream = createExcelFile(excelHead, excelRows);
        return stream;
    }

    /**
     * 生成文件
     * @param excelHead
     * @param excelRows
     * @return
     */
    private static byte[] createExcelFile(List<List<String>> excelHead, List<List<Object>> excelRows){
        try {
            if(CollectionUtils.isNotEmpty(excelHead)){
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                EasyExcel.write(outputStream).excelType(ExcelTypeEnum.XLSX).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                        .head(excelHead)
                        .sheet("sheet1")
                        .doWrite(excelRows);
                return outputStream.toByteArray();
            }
        } catch (Exception e) {
            LOGGER.error("动态生成excel文件失败,headColumns:" + JSONArray.toJSONString(excelHead) + ",excelRows:" + JSONArray.toJSONString(excelRows), e);
        }
        return null;
    }

    public static void main(String[] args) throws IOException {
        //导出包含数据内容的文件(方式一)
        LinkedHashMap<String, String> headColumnMap = new LinkedHashMap<>();
        headColumnMap.put("className","班级");
        headColumnMap.put("name","学生信息,姓名");
        headColumnMap.put("sex","学生信息,性别");
        List<Map<String, Object>> dataList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("className", "一年级");
            dataMap.put("name", "张三" + i);
            dataMap.put("sex", "男");
            dataList.add(dataMap);
        }
        byte[] stream1 = exportExcelFile(headColumnMap, dataList);
        FileOutputStream outputStream1 = new FileOutputStream(new File("easyexcel-export-user.xlsx"));
        outputStream1.write(stream1);
        outputStream1.close();
    }
}

文件内容:

号外!号外!

如果这篇文章对你有所帮助,或者有所启发的话,帮忙点赞、在看、转发、收藏,你的支持就是我坚持下去的最大动力!

一安未来

致力于Java,大数据;心得交流,技术分享;

71篇原创内容

公众号

一文让你了解cookie、session、token、OAuth2

多线程之间的通信方式你能说出几种

Nginx 面试 40 连问:上篇

Nginx 面试 40 连问:下篇

盲目用多线程反而容易导致 OOM

SpringBoot 实现大文件上传下载、分片、断点续传

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值