《SpringBoot》EasyExcel实现百万数据的导入导出

24年11月6日消息,阿里巴巴旗下的Java Excel工具库EasyExcel近日宣布,将停止更新,未来将逐步进入维护模式,将继续修复Bug,但不再主动新增功能。

EasyExcel 是一款知名的 Java Excel 工具库,由阿里巴巴开源,作者是玉箫,在 GitHub 上有 30k+ stars、7.5k forks。 据了解,EasyExcel作者玉箫去年已经从阿里离职,开始创业,也是开源数据库客户端 Chat2DB 的作者。

尽管 EasyExcel 已经不再维护,但其也不失为一个强大且优秀的工具框架,这里我们就一起看一下如何使用 EasyExcel实现百万数据的导入导出。

简介

在日常的开发工作中,Excel 文件的读取和写入是非常常见的需求,特别是在后台管理系统中更为频繁,基于传统的方式操作excel通常比较繁琐,EasyExcel 库的出现为我们带来了更简单、更高效的解决方案。本文将介绍 EasyExcel 库的基本用法和一些常见应用场景,帮助开发者更好地利用 EasyExcel 提高工作效率。

代码地址:GitHub - alibaba/easyexcel: 快速、简洁、解决大文件内存溢出的java处理Excel工具

官网地址:Easy Excel 官网 (alibaba.com)

青出于蓝而胜于蓝

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。

easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

16M内存23秒读取75M(46W行25列)的Excel(3.2.1+版本)

当然还有极速模式能更快,但是内存占用会在100M多一点 !

EasyExcel 核心类

EasyExcel 的核心类主要包括以下几个:

  • ExcelReader: 用于读取 Excel 文件的核心类。通过 ExcelReader 类可以读取 Excel 文件中的数据,并进行相应的处理和操作。

  • ExcelWriter: 用于写入 Excel 文件的核心类。通过 ExcelWriter 类可以将数据写入到 Excel 文件中,并进行样式设置、标题添加等操作。

  • AnalysisEventListener: 事件监听器接口,用于处理 Excel 文件读取过程中的事件,如读取到每一行数据时的操作。

  • AnalysisContext: 读取 Excel 文件时的上下文信息,包括当前行号、sheet 名称等。通过 AnalysisContext 可以获取到读取过程中的一些关键信息。

  • WriteHandler: 写入 Excel 文件时的处理器接口,用于处理 Excel 文件的样式设置、标题添加等操作。

  • WriteSheet: 写入 Excel 文件时的 Sheet 配置类,用于指定写入数据的 Sheet 名称、样式等信息。

这些核心类在 EasyExcel 中承担了不同的角色,协作完成了 Excel 文件的读取和写入操作。开发者可以根据具体的需求和场景,使用这些类来实现 Excel 文件的各种操作。

Alibaba EasyExcel的核心入口类是EasyExcel类,就想我们平时封装的Util类一样,通过它对excel数据读取或者导出。

技术方案

百万数据导入

以下代码源码点击这里

方案一

  • 单线程逐行解析: 使用单个线程顺序地逐行读取 Excel 文件

  • 解析线程单条数据逐条插入:使用解析线程,每读取到一条数据,就立即执行单条插入操作

优点:实现简单,容易调试和维护;适合小数据量的场景。

缺点:效率极低,因为每次插入都要进行一次网络请求和磁盘I/O操作,且没有利用到批量操作的优势。在大量数据的情况下非常耗时。

//方案一,方案二,方案四:单线程逐条解析
public void importExcel(MultipartFile file) throws IOException {
    EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener).doReadAll();
}

EasyExcel解析完之后,都会回调ReadListener方法

@Override
@Transactional(rollbackFor = Exception.class)
public void invoke(Salaries data, AnalysisContext context) {
    //方案一:单线程逐行插入
    saveOne(data);
}

public void saveOne(Salaries data) {
    save(data);
    logger.info("第" + count.getAndAdd(1) + "次插入1条数据");
}

方案二(推荐)

  • 单线程逐行解析:使用一个线程逐行读取 Excel 文件的数据。

  • 解析线程批量插入:使用解析线程,将读取到的数据积累到一定数量后,执行批量插入操作

优点:实现相对简单。比单条插入效率高,减少了网络请求和磁盘I/O的次数

缺点:受限于单线程的处理能力,效率不如多线程,并且在解析过程中可能会消耗大量内存,因为需要先将数据读入内存再进行批量插入。

//方案一,方案二,方案四:单线程逐条解析
public void importExcel(MultipartFile file) throws IOException {
    EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener).doReadAll();
}

@Override
@Transactional(rollbackFor = Exception.class)
public void invoke(Salaries data, AnalysisContext context) {
    salariesList.get().add(data);
    if (salariesList.get().size() >= batchSize) {
		saveData();
    }
}

public void saveData() {
    if (!salariesList.get().isEmpty()) {
        saveBatch(salariesList.get(), salariesList.get().size());
        logger.info("第" + count.getAndAdd(1) + "次插入" + salariesList.get().size() + "条数据");
        salariesList.get().clear();
    }
}

方案三

  • 多线程解析:每个Sheet对应一个线程进行解析,多个线程并行解析不同的Sheet。

  • 解析线程批量插入:使用解析的线程,将读取到的数据积累到一定数量后,执行批量插入操作。

优点:多线程解析可以充分利用多核CPU的优势,提高解析速度;批量插入可以减少数据库的事务开销和I/O操作。

缺点:需要考虑多线程之间的数据传输和同步的问题,设计和实现较复杂。可能在解析和插入之间存在性能瓶颈。例如,若插入较快,则需要等待解析完成才可进行插入,因此可用异步线程的方式

//方案三,方案四:多线程解析,一个sheet对应一个线程
public void importExcelAsync(MultipartFile file) {
    // 开20个线程分别处理20个sheet
    List < Callable < Object >> tasks = new ArrayList < > ();
    for (int i = 0; i < 20; i++) {
        int num = i;
        tasks.add(() - > {
            EasyExcel.read(file.getInputStream(), Salaries.class, salariesListener)
                .sheet(num)
                .doRead();
            return null;
        });
    }

    try {
        executorService.invokeAll(tasks);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

@Override
@Transactional(rollbackFor = Exception.class)
public void invoke(Salaries data, AnalysisContext context) {
    //方案二、三、四、五
    salariesList.get().add(data);
    if (salariesList.get().size() >= batchSize) {
		saveData();
    }
}

方案四(较推荐)

  • 单线程逐行解析:使用一个线程逐行读取 Excel 文件的数据。

  • 异步线程批量插入:另开一个线程将数据批量插入到数据库,解析线程可以继续执行后续的解析操作。(可以只使用一个线程,防止多线程插入导致锁竞争的问题)

优点:解析和插入操作分离,可以在解析的同时进行插入,提高效率。

缺点:受限于单线程解析速度,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值