一文教你快速上手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

    
        com.alibaba
        fastjson
        1.2.83
   
   
        org.projectlombok
        lombok
        1.18.6
    
    
    
        com.alibaba
        easyexcel
        3.0.5
    

读文件

注意:方便测试,暂未使用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> {
    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> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

     /** 这里会一行行的返回头
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map headMap, AnalysisContext context) {
        LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }
    /**
     * 这个每一条数据解析都会来调用
     * @param data
     * @param context
     */
    @Override
    public void invoke(Map 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> 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 headColumnMap, List> dataList){
        //获取列名称
        List> excelHead = new ArrayList<>();
        if(MapUtils.isNotEmpty(headColumnMap)){
            //key为匹配符,value为列名,如果多级列名用逗号隔开
            headColumnMap.entrySet().forEach(entry -> {
                excelHead.add(CollectionUtil.newArrayList(entry.getValue().split(",")));
            });
        }
        List> excelRows = new ArrayList<>();
        if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){
            for (Map dataMap : dataList) {
                List 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> excelHead, List> 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 headColumnMap = new LinkedHashMap<>();
        headColumnMap.put("className","班级");
        headColumnMap.put("name","学生信息,姓名");
        headColumnMap.put("sex","学生信息,性别");
        List> dataList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Map 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();
    }
}

文件内容:

一文教你快速上手EasyExcel_第1张图片

号外!号外!

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

一文教你快速上手EasyExcel_第2张图片

一安未来

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

71篇原创内容

公众号

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

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

Nginx 面试 40 连问:上篇

Nginx 面试 40 连问:下篇

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

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

你可能感兴趣的:(java,spring,开发语言)