EasyExcel 如何导出大量数据 和 并发测试大量数据导出

官方使用文档:https://alibaba-easyexcel.github.io/

前言

  1. EasyExcel怎么避免OOM的?
  2. 大量数据导出怎么处理?
WriteWorkbook对象字段解析
ExcelReaderBuilder ExcelWriterBuilder
WriteWorkbook 对象  --> 一个excel文件对象

相当于一个excel 通过 ExcelWriterBuilder 构建, 就是该文件的一些特性和一些基础信息(这里的不全,可以自己点进去看),后面还有继承的WriteBasicParameter 类。

public class WriteWorkbook extends WriteBasicParameter {
    /**
     * CSV(".csv"),
     * XLS(".xls"),
     * XLSX(".xlsx")	默认xlsx
     */
    private ExcelTypeEnum excelType;
    /**
     * Default true.
     */
    private Boolean autoCloseStream;
    /**
     * 强制使用的inputStream .Default is false
     */
    private Boolean mandatoryUseInputStream;
    /**
     * Whether the encryption  excel是否要加密, 加密需要整个读到内存进行处理,一般不用(耗费内存)
     */
    private String password;
    /**
     * 在内存中写入excel。默认为false,创建缓存文件并最终写入excel。  
     * <p>
     * Comment and RichTextString are only supported in memory mode.
     */
    private Boolean inMemory;
    /**
     * Excel也会在抛出异常的情况下编写.默认 false.
     */
    private Boolean writeExcelOnException;
}

inMemory=flase,可以看出, 默认是通过流先读到磁盘,而不是在内存处理好在返回。因此,配合数据库 流式处理或者游标 可处理大数据。

创建文件对象
public class WorkBookUtil {

    private WorkBookUtil() {}

    public static void createWorkBook(WriteWorkbookHolder writeWorkbookHolder) throws IOException {
        switch (writeWorkbookHolder.getExcelType()) {
            case XLSX:
            	// 是否用临时文件流
                if (writeWorkbookHolder.getTempTemplateInputStream() != null) {
                    XSSFWorkbook xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTempTemplateInputStream());
                    writeWorkbookHolder.setCachedWorkbook(xssfWorkbook);
                    if (writeWorkbookHolder.getInMemory()) {
                        writeWorkbookHolder.setWorkbook(xssfWorkbook);
                    } else {
                        writeWorkbookHolder.setWorkbook(new SXSSFWorkbook(xssfWorkbook));
                    }
                    return;
                }
                Workbook workbook;
                // 这里可以看到是否在内存中写excel。默认false,创建缓存文件,最后写入excel。
                if (writeWorkbookHolder.getInMemory()) {
                    workbook = new XSSFWorkbook();
                } else {
                    workbook = new SXSSFWorkbook();
                }
                writeWorkbookHolder.setCachedWorkbook(workbook);
                writeWorkbookHolder.setWorkbook(workbook);
                return;
    }
}
创建行对象写入磁盘

writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream());

org.apache.poi.xssf.streaming.SXSSFSheet#createRow							// 创建行

private int _randomAccessWindowSize = SXSSFWorkbook.DEFAULT_WINDOW_SIZE;	// 默认100行后开始刷到磁盘(MySql分页读取可以大于100条进行写入)

org.apache.poi.xssf.streaming.SXSSFSheet#flushRows(int)						// 把treeMap第一个节点刷到磁盘(临时文件)

在这里插入图片描述
在这里插入图片描述

上面两张图得出结论: 先读取100行数据到内存, 然后后面每读一行数据刷到磁盘中。执行写入方法后不会立刻刷盘,系统会有个缓冲区,到达一定大小后才会刷入到磁盘文件中。
疑惑1:为什么要先加载100条才开始一条条的刷入磁盘?
疑惑2:为什么存储行节点的_rows使用 TreeMap数据类型,为什么不用Queue?
groupRow():将一系列行捆绑在一起,以便它们可以折叠或展开

org.apache.poi.xssf.streaming.SXSSFSheet#dispose	// 关闭写流,剩余写缓存刷新到磁盘

临时文件:
在这里插入图片描述

如果导出完还要对表的一些数据进行处理标注的操作;EasyExcel只能从磁盘重新读取数据到内存。所以一般只能再解析读入磁盘前进行处理。一般可以用它给出的接口RowWriteHandler,SheetWriteHandler,CellWriteHandler。

com.alibaba.excel.write.executor.ExcelWriteAddExecutor#addOneRowOfDataToExcel

在这里插入图片描述

测试10w条数据导出
  1. 流式查询分批导入
    先查询100条 --> excel.write --> 磁盘
    在这里插入图片描述
mysql查询花费时间 num=100000   程序执行花费时间:74280
写入磁盘花费花费时间:2406
  1. 一次性查询全部导出
    在这里插入图片描述
mysql查询花费时间 num=100000 costTime=67051
程序执行花费时间:70621
  1. for循环( 分页查询 -> write)
    在这里插入图片描述
程序执行花费时间:153209
结论
  1. EasyExcel不是一次性写入内存,所以无需一次性向MySql查询全部数据到内存中。
  2. 不能使用 for循环 分页查询 -> write 操作。虽然内存不会爆掉,但是很慢。5w条数据,每次100条,需要查数据库500次。(使用流式查询)
  3. 流式查询处理:
    1. 长连接:无需多次链接数据库。减少TCP链接消耗。
    2. 逐条读取:读指定条数, 进行write操作写到磁盘,减少对象堆积,内存不会爆掉。

消耗内存: 流式导入 < for循环( 分页查询 -> write) < 一次性查询全部导出
消耗时间:一次性查询全部导出 < 流式导入 < for循环( 分页查询 -> write)

预约导出批量查询导出操作

思路:流式查询处理(长连接,逐条读取), 读取几条就写入磁盘,不会耗费太多内存。
流式查询处理缺点:

  1. 占用数据库连接,直到关闭。
  2. 少量数据没有一次性查询快,适合使用在预约导出。(通过MQ拿到导出消息,后台慢慢处理。)
多线程并发导出测试

JVM参数

-server
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=128m
-Xms250m
-Xmx250m
-XX:SurvivorRatio=8
-XX:+UseConcMarkSweepGC
-Dfile.encoding=UTF-8
  1. 10条线程并发 导出1w条数据。
    在这里插入图片描述

在这里插入图片描述

结果:
num=10000   程序执行花费时间:6490
写入磁盘花费花费时间:452
num=10000   程序执行花费时间:7128
写入磁盘花费花费时间:674
num=10000   程序执行花费时间:15926
写入磁盘花费花费时间:891
num=10000   程序执行花费时间:18550
写入磁盘花费花费时间:1106
num=10000   程序执行花费时间:29579
写入磁盘花费花费时间:1384
num=10000   程序执行花费时间:30020
写入磁盘花费花费时间:1782
num=10000   程序执行花费时间:30147
写入磁盘花费花费时间:1806
num=10000   程序执行花费时间:34828
写入磁盘花费花费时间:2038
num=10000   程序执行花费时间:35845
写入磁盘花费花费时间:2247
num=10000   程序执行花费时间:37518
写入磁盘花费花费时间:2460
  1. 10条线程并发导出5w数据
    在这里插入图片描述
num=50000   程序执行花费时间:134359
写入磁盘花费花费时间:1308
num=50000   程序执行花费时间:182012
写入磁盘花费花费时间:2362
num=50000   程序执行花费时间:194460
写入磁盘花费花费时间:3428
num=50000   程序执行花费时间:212755
写入磁盘花费花费时间:4500
num=50000   程序执行花费时间:235474
写入磁盘花费花费时间:5549
num=50000   程序执行花费时间:243594
写入磁盘花费花费时间:6570
num=50000   程序执行花费时间:248091
写入磁盘花费花费时间:7576
2022-06-16 18:48:25.452  WARN 15440 --- [nio-8088-exec-4] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@4a4b85d9 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.
2022-06-16 18:48:25.472 ERROR 15440 --- [nio-8088-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.TransientDataAccessResourceException: 
### Error querying database.  Cause: java.sql.SQLException: SSL peer shut down incorrectly

Possibly consider using a shorter maxLifetime value. 应该是长连接超过最长时间导致的。但是并不会出现OOM。应该提高下内存即可,否则10条线程读取大量数据会占用大量内存,CPU上下文切换也会增大负担。

待完善…

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: EasyExcel是一个基于Java的简单易用的Excel操作工具,它提供了批量数据导出的功能。 在大量数据导出时,EasyExcel具有以下优点: 1. 速度快:EasyExcel采用了基于事件模型的解析方式,可以处理非常大的Excel文件而不会引起内存溢出。它可以将数据直接写到OutputStream中,避免了频繁的IO操作,因此导出速度非常快。 2. 内存消耗较低:EasyExcel使用了一种流式写入的方式,将数据逐行写入Excel文件,这样可以很好地控制内存的消耗。即使处理非常大的数据集,也不会出现内存溢出的问题。 3. 支持多种数据源:EasyExcel支持从各种数据源中读取数据,如数据库、集合、文件等,并可以将数据导出到Excel中。这样可以满足不同场景下的数据导出需求。 4. 操作简单:EasyExcel提供了简单易用的API,可以方便地配置导出的格式、样式、列宽等。同时,它还支持导出多个Sheet,可以根据具体需求进行灵活配置。 5. 良好的兼容性:EasyExcel对Excel文件的兼容性非常好,支持导出.xlsx和.xls格式的文件,同时还支持设置密码、加密和设置单元格样式等功能。这样可以满足不同用户的需求。 无论是导出少量数据还是大量数据EasyExcel都能够快速高效地完成任务。它的简单易用性、低内存消耗和良好的兼容性使其成为处理大量数据导出的理想工具。 ### 回答2: EasyExcel是一款用于Excel文件读写操作的Java工具库,它提供了简单易用的API以实现大量数据导出功能。以下是使用EasyExcel进行大量数据导出的步骤: 1. 引入EasyExcel库:首先需要在Java项目中引入EasyExcel的依赖库,可以通过Maven或者Gradle等构建工具进行引入。 2. 创建导出模板:在Excel文件中,先创建一个模板,定义好数据的表头和格式。可以指定每列的标题、列宽、格式等,以满足不同的需求。 3. 构建数据源:准备好需要导出数据源,可以通过数据库查询、文件读取等方式获取数据。 4. 创建导出任务:使用EasyExcel的API创建一个导出任务,并指定导出的Excel文件路径、模板等参数。 5. 填充数据:通过遍历数据源,将数据逐行填充到Excel文件中。可以使用EasyExcel提供的write方法将数据写入到指定的Sheet中。 6. 设置样式和格式:根据需求,可以对导出数据进行样式和格式的设置,如设置单元格的字体、背景色、边框等。 7. 执行导出操作:执行导出任务,通过调用EasyExcel的API将数据写入到Excel文件中。 8. 导出结果处理:根据导出的结果进行相应的处理,如判断是否导出成功、记录日志等。 使用EasyExcel进行大量数据导出的优势在于其简洁易用的API,可以轻松处理大量数据导出需求。同时,EasyExcel导出速度较快,可以高效地处理大规模的数据导出任务。另外,EasyExcel对于复杂的数据格式和样式也有很好的支持,可以满足不同场景下的导出需求。 ### 回答3: EasyExcel是一款简单易用的Java Excel工具类库,广泛应用于数据导出和导入的场景。对于大量数据导出EasyExcel提供了高效的解决方案。 首先,EasyExcel采用了基于流式操作的方式来处理数据导入导出,不会一次性将所有数据读入内存,而是通过分批加载数据,减小了内存的占用。这样即使数据量很大,也能够快速导出。 其次,EasyExcel通过多线程的方式来提升导出速度。通过配置线程数,可以将任务分发给多个线程同时处理,加快导出的速度。同时,EasyExcel还支持设置每个线程处理的数据量,可以根据服务器性能进行灵活调整,进一步提高导出效率。 另外,EasyExcel还支持将导出任务拆分为多个子任务进行处理,可以将数据按照某种规则或条件进行拆分,每个子任务独立导出。这样可以充分利用服务器的多核资源,加速导出进程。 总之,EasyExcel通过分批加载数据、多线程处理和子任务拆分等方式,有效解决了大量数据导出的问题。无论是导出百万、千万、亿级的数据,都能够高效快速地完成导出任务。同时,EasyExcel还提供了丰富的API和灵活的配置,方便开发者根据具体需求进行定制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值