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 文件的数据。
-
异步线程批量插入:另开一个线程将数据批量插入到数据库,解析线程可以继续执行后续的解析操作。(可以只使用一个线程,防止多线程插入导致锁竞争的问题)
优点:解析和插入操作分离,可以在解析的同时进行插入,提高效率。
缺点:受限于单线程解析速度,