百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

SpringBoot + EasyExcel 轻松实现百万级数据导入导出,用起来还优雅

csdh11 2025-03-10 14:35 2 浏览

01、背景介绍

在实际的业务系统开发过程中,操作 Excel 实现数据的导入导出基本上是个非常常见的需求。

之前,我们有介绍一款非常好用的工具:EasyPoi,有读者提出在数据量大的情况下,EasyPoi 会占用内存大,性能不够好,严重的时候,还会出现内存异常的现象。

今天我给大家推荐一款性能更好的 Excel 导入导出工具:EasyExcel,希望对大家有所帮助!

easyexcel 是阿里开源的一款 Excel导入导出工具,具有处理速度快、占用内存小、使用方便的特点,底层逻辑也是基于 apache poi 进行二次开发的,目前的应用也是非常广!

相比 EasyPoi,EasyExcel 的处理数据性能非常高,读取 75M (46W行25列) 的Excel,仅需使用 64M 内存,耗时 20s,极速模式还可以更快!

废话也不多说了,下面直奔主题!

02、方案实践

在 SpringBoot 项目中集成 EasyExcel 其实非常简单,仅需一个依赖即可。



    com.alibaba
    easyexcel
    3.0.5

EasyExcel 的导出导入支持两种方式进行处理

  • 第一种是通过实体类注解方式来生成文件和反解析文件数据映射成对象
  • 第二种是通过动态参数化生成文件和反解析文件数据

下面我们以用户信息的导出导入为例,分别介绍两种处理方式。

2.1、简单导出

首先,我们只需要创建一个UserEntity用户实体类,然后添加对应的注解字段即可,示例代码如下:

public class UserWriteEntity {

    @ExcelProperty(value = "姓名")
    private String name;
    
    @ExcelProperty(value = "年龄")
    private int age;
    
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "操作时间")
    private Date time;
    
    //set、get...
}

然后,使用 EasyExcel 提供的EasyExcel工具类,即可实现文件的导出。

public static void main(String[] args) throws FileNotFoundException {
    List dataList = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        UserWriteEntity userEntity = new UserWriteEntity();
        userEntity.setName("张三" + i);
        userEntity.setAge(20 + i);
        userEntity.setTime(new Date(System.currentTimeMillis() + i));
        dataList.add(userEntity);
    }
    //定义文件输出位置
    FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user1.xlsx"));
    EasyExcel.write(outputStream, UserWriteEntity.class).sheet("用户信息").doWrite(dataList);
}

运行程序,打开文件内容结果!

2.2、简单导入

这种简单固定表头的 Excel 文件,如果想要读取文件数据,操作也很简单。

以上面的导出文件为例,使用 EasyExcel 提供的EasyExcel工具类,即可来实现文件内容数据的快速读取,示例代码如下:

首先创建读取实体类

/**
 * 读取实体类
 */
public class UserReadEntity {

    @ExcelProperty(value = "姓名")
    private String name;
    /**
     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
     */
    @ExcelProperty(index = 1)
    private int age;
    
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    @ExcelProperty(value = "操作时间")
    private Date time;
    
    //set、get...
}

然后读取文件数据,并封装到对象里面

public static void main(String[] args) throws FileNotFoundException {
    //同步读取文件内容
    FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-user1.xls"));
    List list = EasyExcel.read(inputStream).head(UserReadEntity.class).sheet().doReadSync();
    System.out.println(JSONArray.toJSONString(list));
}

运行程序,输出结果如下:

[{"age":20,"name":"张三0","time":1616920360000},{"age":21,"name":"张三1","time":1616920360000},{"age":22,"name":"张三2","time":1616920360000},{"age":23,"name":"张三3","time":1616920360000},{"age":24,"name":"张三4","time":1616920360000},{"age":25,"name":"张三5","time":1616920360000},{"age":26,"name":"张三6","time":1616920360000},{"age":27,"name":"张三7","time":1616920360000},{"age":28,"name":"张三8","time":1616920360000},{"age":29,"name":"张三9","time":1616920360000}]

2.3、动态自由导出导入

在实际使用开发中,我们不可能每来一个 excel 导入导出需求,就编写一个实体类,很多业务需求需要根据不同的字段来动态导入导出,没办法基于实体类注解的方式来读取文件或者写入文件。

因此,基于EasyExcel提供的动态参数化生成文件和动态监听器读取文件方法,我们可以单独封装一套动态导出导出工具类,省的我们每次都需要重新编写大量重复工作,以下就是小编我在实际使用过程,封装出来的工具类,在此分享给大家!

  • 首先,我们可以编写一个动态导出工具类
public class DynamicEasyExcelExportUtils {

    private static final Logger log = LoggerFactory.getLogger(DynamicEasyExcelExportUtils.class);

    private static final String DEFAULT_SHEET_NAME = "sheet1";

    /**
     * 动态生成导出模版(单表头)
     * @param headColumns 列名称
     * @return            excel文件流
     */
    public static byte[] exportTemplateExcelFile(List headColumns){
        List<List> excelHead = Lists.newArrayList();
        headColumns.forEach(columnName -> { excelHead.add(Lists.newArrayList(columnName)); });
        byte[] stream = createExcelFile(excelHead, new ArrayList<>());
        return stream;
    }

    /**
     * 动态生成模版(复杂表头)
     * @param excelHead   列名称
     * @return
     */
    public static byte[] exportTemplateExcelFileCustomHead(List<List> excelHead){
        byte[] stream = createExcelFile(excelHead, new ArrayList<>());
        return stream;
    }

    /**
     * 动态导出文件(通过map方式计算)
     * @param headColumnMap  有序列头部
     * @param dataList       数据体
     * @return
     */
    public static byte[] exportExcelFile(LinkedHashMap headColumnMap, List<Map> dataList){
        //获取列名称
        List<List> excelHead = new ArrayList<>();
        if(MapUtils.isNotEmpty(headColumnMap)){
            //key为匹配符,value为列名,如果多级列名用逗号隔开
            headColumnMap.entrySet().forEach(entry -> {
                excelHead.add(Lists.newArrayList(entry.getValue().split(",")));
            });
        }
        List<List<Object>> excelRows = new ArrayList<>();
        if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){
            for (Map 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 rowHeads
     * @param excelRows
     * @return
     */
    public static byte[] customerExportExcelFile(List<List> rowHeads, List<List<Object>> excelRows){
        //将行头部转成easyexcel能识别的部分
        List<List> excelHead = transferHead(rowHeads);
        return createExcelFile(excelHead, excelRows);
    }

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

    /**
     * 将行头部转成easyexcel能识别的部分
     * @param rowHeads
     * @return
     */
    public static List<List> transferHead(List<List> rowHeads){
        //将头部列进行反转
        List<List> realHead = new ArrayList<>();
        if(CollectionUtils.isNotEmpty(rowHeads)){
            Map<Integer, List> cellMap = new LinkedHashMap<>();
            //遍历行
            for (List cells : rowHeads) {
                //遍历列
                for (int i = 0; i < cells.size i ifcellmap.containskeyi cellmap.geti.addcells.geti else cellmap.puti lists.newarraylistcells.geti realhead cellmap.entryset.foreachitem -> realHead.add(item.getValue()));
        }
        return realHead;
    }

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


        //导出包含数据内容的文件(方式二)
        //头部,第一层
        List head1 = new ArrayList<>();
        head1.add("第一行头部列1");
        head1.add("第一行头部列1");
        head1.add("第一行头部列1");
        head1.add("第一行头部列1");
        //头部,第二层
        List head2 = new ArrayList<>();
        head2.add("第二行头部列1");
        head2.add("第二行头部列1");
        head2.add("第二行头部列2");
        head2.add("第二行头部列2");
        //头部,第三层
        List head3 = new ArrayList<>();
        head3.add("第三行头部列1");
        head3.add("第三行头部列2");
        head3.add("第三行头部列3");
        head3.add("第三行头部列4");

        //封装头部
        List<List> allHead = new ArrayList<>();
        allHead.add(head1);
        allHead.add(head2);
        allHead.add(head3);

        //封装数据体
        //第一行数据
        List<Object> data1 = Lists.newArrayList(1,1,1,1);
        //第二行数据
        List<Object> data2 = Lists.newArrayList(2,2,2,2);
        List<List<Object>> allData = Lists.newArrayList(data1, data2);

        byte[] stream2 = customerExportExcelFile(allHead, allData);
        FileOutputStream outputStream2 = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user6.xlsx"));
        outputStream2.write(stream2);
        outputStream2.close();


    }
}
  • 然后,编写一个动态导入工具类
/**
 * 创建一个文件读取监听器
 */
public class DynamicEasyExcelListener extends AnalysisEventListener<Map> {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserDataListener.class);
    /**
     * 表头数据(存储所有的表头数据)
     */
    private List<Map> headList = new ArrayList<>();
    /**
     * 数据体
     */
    private List<Map> dataList = new ArrayList<>();
    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map headMap, AnalysisContext context) {
        LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
        //存储全部表头数据
        headList.add(headMap);
    }
    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(Map data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        dataList.add(data);
    }
    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        LOGGER.info("所有数据解析完成!");
    }
    public List<Map> getHeadList() {
        return headList;
    }
    public List<Map> getDataList() {
        return dataList;
    }
}
  • 动态导入工具类
/**
 * 编写导入工具类
 */
public class DynamicEasyExcelImportUtils {
    /**
     * 动态获取全部列和数据体,默认从第一行开始解析数据
     * @param stream
     * @return
     */
    public static List<Map> parseExcelToView(byte[] stream) {
        return parseExcelToView(stream, 1);
    }
    /**
     * 动态获取全部列和数据体
     * @param stream           excel文件流
     * @param parseRowNumber   指定读取行
     * @return
     */
    public static List<Map> parseExcelToView(byte[] stream, Integer parseRowNumber) {
        DynamicEasyExcelListener readListener = new DynamicEasyExcelListener();
        EasyExcelFactory.read(new ByteArrayInputStream(stream)).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet(0).doRead();
        List<Map> headList = readListener.getHeadList();
        if(CollectionUtils.isEmpty(headList)){
            throw new RuntimeException("Excel未包含表头");
        }
        List<Map> dataList = readListener.getDataList();
        if(CollectionUtils.isEmpty(dataList)){
            throw new RuntimeException("Excel未包含数据");
        }
        //获取头部,取最后一次解析的列头数据
        Map excelHeadIdxNameMap = headList.get(headList.size() -1);
        //封装数据体
        List<Map> excelDataList = Lists.newArrayList();
        for (Map dataRow : dataList) {
            Map rowData = new LinkedHashMap<>();
            excelHeadIdxNameMap.entrySet().forEach(columnHead -> {
                rowData.put(columnHead.getValue(), dataRow.get(columnHead.getKey()));
            });
            excelDataList.add(rowData);
        }
        return excelDataList;
    }
    /**
     * 文件导入测试
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx"));
        byte[] stream = IoUtils.toByteArray(inputStream);
        List<Map> dataList = parseExcelToView(stream, 2);
        System.out.println(JSONArray.toJSONString(dataList));
        inputStream.close();
    }
}

为了方便后续的操作流程,在解析数据的时候,会将列名作为key

03、小结

在实际的业务开发过程中,根据参数动态实现 Excel 的导出导入还是非常广的。

当然,EasyExcel 的功能还不只上面介绍的那些内容,还有基于模版进行 excel的填充,web 端 restful 的导出导出,使用方法大致都差不多!

示例代码地址:

https://gitee.com/pzblogs/spring-boot-example-demo

相关推荐

Hutool Java工具类库导出Excel,超级简单

作者:程序猿的内心独白原文链接:http://suo.im/5Zxx2L前言在开发应用系统的时候,导出文件是必不可放的功能。以前用过POI、easyexcel等工具的导入导出功能,但总感觉太麻烦了,代...

java轻松玩转Excel之EasyExcel

项目地址:https://github.com/PiKeZhao/excel-model.git如果您对该项目有什么问题加群咨询哦978219630(各类电子书籍,学习视频等)大家常用Apache...

程序员:超级简单导出Excel 工具,Hutool Java工具类库

前言在开发应用系统的时候,导出文件是必不可放的功能。以前用过POI、easyexcel等工具的导入导出功能,但总感觉太麻烦了,代码特别多,感觉并不是很好用。今天给大家介绍一款新工具,java工具类库H...

EasyExcel导出Excel表格到浏览器,通过Postman测试导出Excel

一、前言小编最近接到一个导出Excel的需求,需求还是很简单的,只需要把表格展示的信息导出成Excel就可以了,也没有复杂的合并列什么的。...

好难~记录一次生产上的OOM解决过程

点击上方,轻松关注!及时获取有趣有料的技术文章记录一次生产上的OOM解决过程一.项目架构...

发现开源:替换Jenkins,支持多用户多语言部署平台Walle很震撼

溪云阁:专注编程教学,架构,JAVA,Python,微服务,机器学习等领域,欢迎关注,一起学习。部署系统,从手动部署,到用jenkins,到阿里的自动化运维部署,一直在不断的变化姿势,好让自己舒服点。...

SpringBoot + EasyExcel 轻松实现百万级数据导入导出,用起来还优雅

01、背景介绍在实际的业务系统开发过程中,操作Excel实现数据的导入导出基本上是个非常常见的需求。...

10W 行级别数据的 Excel 导入优化记录

优质文章,及时送达作者:后青春期的Keatswww.cnblogs.com/keatsCoder/p/13217561.html需求说明项目中有一个Excel导入的需求:缴费记录导入...

easypoi导出Excel根据内容如何自动换行和自动调整行高

在使用easypoi导出EXCEL的时候,经常会遇到需要根据内容自动换行和自动调整行高的情况...

POI读取/生成Excel大文件,有高人吗?

最近在搞一个通用文件处理服务器,需要处理excel文件,但是有的excel文件大小超过3M,读取文件时直接导致jvm异常:gcoverheadlimitexceeded。原来poi读取excel...

如何轻松实现Excel动态列导出?Easypoi教程来袭!

EasyPoi简介EasyPoi是一款基于ApachePOI和jxls的Java开源框架,它可以用于快速创建Excel、Word、Pdf等复杂文档。Easypoi最大的特点是可以通过注解来实现对Ex...

Excel导出,Excel模板导出Excel导入,Word模板导出神器-Easypoi

概述Easypoi是码云上的一个开源项目。项目开发组织是Lemur开源。目前属于码云最有价值开源项目。Easypoi是对poi的封装,其主打的功能就是容易,让一个没见接触过poi的人员就可以方便...

优雅地实现EasyPoi动态导出列的两种方式

前言嗨,大家好,我是希留。...

easypoi一款方便快捷的excel处理框架,赶紧学习起来

easypoi是一个基于ApachePOI和jxls封装的Java框架,用于简化Excel导入导出和Word导出等操作。它支持将Java对象转换为Excel或Word文档,并支持Excel模板导出和...

EasyPoi使用

EasyPoi的主要特点:1.设计精巧,使用简单2.接口丰富,扩展简单3.默认值多,writelessdomore4.springmvc支持,web导出可以简单明了使用1.easypoi...