当前位置:首页 > 技术 > 正文内容

使用 EasyExcel 导出 Excel 的性能分析与实践

访客 技术 2026年9月22日 12

本文旨在探讨使用 EasyExcel 导出大量数据到 Excel 时可能遇到的性能问题,并通过代码示例和实验数据分析不同导出策略(单 Sheet 页 vs. 多 Sheet 页)对导出和打开速度的影响。

核心代码示例

以下 Java 代码展示了如何使用 EasyExcel 将大量数据导出到 Excel 文件。代码中包含了两种导出方法:一种是创建多个 Sheet 页,另一种是使用单个 Sheet 页。

导出多 Sheet 页 (downExcel 方法)


package org.example.easyexcel.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.example.easyexcel.entity.ExportDataModel; // 假设这是你的数据实体类
import org.example.easyexcel.service.IExportDataService; // 假设这是你的服务接口
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Slf4j
@ResponseBody
@Controller
public class ExcelExportController {

    @Autowired
    IExportDataService exportDataService; // 注入服务层

    @GetMapping("download-multi-sheet/{recordCount}")
    public void downloadMultiSheet(HttpServletResponse response, @PathVariable("recordCount") Integer recordCount) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("多Sheet导出示例", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        List<ExportDataModel> dataList = generateMockData(recordCount);
        long startTime = System.currentTimeMillis();

        // 使用 try-with-resources 确保 ExcelWriter 被正确关闭
        try (ExcelWriter writer = EasyExcel.write(response.getOutputStream(), ExportDataModel.class).build()) {
            // 示例:导出 5 个 Sheet 页
            for (int i = 0; i < 5; i++) {
                // 必须为每个 Sheet 指定唯一的 sheetNo 和 sheetName
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "数据模板_" + i).build();
                // 实际应用中,这里的 dataList 应该根据分页从数据库查询
                writer.write(dataList, writeSheet);
            }
        }

        log.info("多 Sheet 页导出耗时: {} ms", System.currentTimeMillis() - startTime);
    }

    /**
     * 文件下载 (单 Sheet 页)
     * @param response HttpServletResponse
     * @param recordCount 要导出的记录数量
     */
    @GetMapping("/download-single-sheet/{recordCount}")
    public void downloadSingleSheet(HttpServletResponse response, @PathVariable("recordCount") Integer recordCount) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("单Sheet导出示例", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        List<ExportDataModel> itemData = generateMockData(recordCount);
        long startTime = System.currentTimeMillis();

        // 直接使用 doWrite 方法导出到单个 Sheet
        EasyExcel.write(response.getOutputStream(), ExportDataModel.class)
                 .sheet("汇总数据")
                 .doWrite(itemData);

        log.info("单 Sheet 页导出耗时: {} ms", System.currentTimeMillis() - startTime);
    }

    /**
     * 模拟从数据库查询数据的逻辑
     * @param start 起始索引
     * @param end 结束索引
     * @return 数据列表
     */
    private Collection<ExportDataModel> queryDataFromDb(int start, int end) {
        QueryWrapper<ExportDataModel> query = new QueryWrapper<>();
        query.last("limit " + start + "," + end); // 假设数据库支持 limit
        List<ExportDataModel> list = exportDataService.list(query);
        log.info("从数据库查询到 {} 条记录", list.size());
        return list;
    }

    /**
     * 生成模拟数据
     * @param count 数据量
     * @return 模拟数据列表
     */
    private List<ExportDataModel> generateMockData(int count) {
        LocalDateTime now = LocalDateTime.now();
        ExportDataModel mockRecord = new ExportDataModel()
                .setAsyncId("MOCK_ID_123")
                .setAsyncType("MOCK_TYPE")
                .setRoutingKey("mock.routing.key")
                .setExchange("mock.exchange")
                .setParamClassName("org.example.easyexcel.entity.ExportDataModel")
                .setParamJsonDetail("{}")
                .setExecStatus("INIT")
                .setUpdateTime(now)
                .setCreateTime(now)
                .setExecCount(1)
                .setSendCount(0)
                .setExtInfo("{}");

        List<ExportDataModel> generatedList = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            // 为确保数据不完全重复,可以稍微修改
            mockRecord.setAsyncId("MOCK_ID_" + i);
            generatedList.add(mockRecord);
        }
        log.info("生成 {} 条模拟数据", generatedList.size());
        return generatedList;
    }

    /*
     * // 文件上传示例 (注释掉,仅供参考)
     * @PostMapping("upload")
     * @ResponseBody
     * public String uploadFile(MultipartFile file) throws IOException {
     *     EasyExcel.read(file.getInputStream(), ExportDataModel.class, new UploadDataListener(uploadDAO)).sheet().doRead();
     *     return "Upload successful";
     * }
     */
}
    

性能测试与分析

在排除数据库 I/O 耗时后,我们对导出百万级数据的性能进行了测试:

  • 导出耗时:
    • 100,000 条数据:约 2.x 秒
    • 500,000 条数据:约 12-13 秒
    • 1,000,000 条数据:约 26-28 秒
  • 多 Sheet 页 (10 个 Sheet) vs. 单 Sheet 页: 在导出大量数据(如一百万条)时,多 Sheet 页和单 Sheet 页的导出时间几乎没有显著差异。
  • 打开 Excel 文件:
    • 100,000 条数据 (单 Sheet):打开速度非常快。
    • 1,000,000 条数据 (单 Sheet):打开需要约 14 秒。
    • 多 Sheet 页 (10 页) 和单 Sheet 页在打开速度上表现相似,差异不大。

结论与建议

EasyExcel 在导出大量数据时表现出良好的性能。在导出时间方面,单 Sheet 页和多 Sheet 页策略的差异可以忽略不计。然而,在打开超大 Excel 文件时,文件大小本身会成为瓶颈,因此,对于超大规模数据,应考虑其他数据展示方案或优化 Excel 文件结构。

重要提示: EasyExcel 本身在写入数据时已内置多线程优化。然而,根据官方文档和社区反馈,EasyExcel 不允许在多线程环境下同时操作多个 Sheet 页。如果在导出过程中手动启用了多线程处理多个 Sheet,可能会导致不可预测的问题。这可能是多 Sheet 页导出性能未显著优于单 Sheet 页的原因之一。

因此,在实际应用中,如果业务逻辑不需要强制划分多个 Sheet,使用单 Sheet 页导出通常更简单且性能表现稳定。如果必须使用多 Sheet,请确保按顺序写入,避免并发写入的风险。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。