一、一个“小需求”,差点搞垮生产环境
上周五下午 4 点,产品突然找到我:
“财务那边要导出近一年的所有订单明细,大概一千万条,做成 Excel 发给他们。今天能搞定吗?”
我心里一沉——一千万条?Excel?
但为了 KPI,只能硬着头皮答应:“行,我试试。”
我立刻写了个接口,用熟悉的 EasyExcel 导出:
List<Order> orders = orderMapper.selectAll(); // 千万级全量查询EasyExcel.write(outputStream, Order.class).sheet("订单").doWrite(orders);
结果刚上线,监控告警就炸了:
JVM 内存飙升至 90%
GC 频繁 Full GC
分钟后服务 OOM 崩溃
运维在群里怒吼:“又来?这周第 3 次了!”
我意识到:EasyExcel 虽好,但在超大数据量导出场景下,并非最优解。
于是,我开始寻找更轻量、更高效的替代方案——直到遇见它:FastExcel。
二、FastExcel 是什么?为什么它这么快?
1. 背景介绍
FastExcel 是由法国公司 Dhatim 开源的一个纯 Java Excel 写入库(GitHub: dhatim/fastexcel:github.com/dhatim/fastexcel)。
它不依赖 Apache POI,而是直接操作 XML 流,将数据以 SAX(流式)方式写入 .xlsx 文件。
.xlsx 本质是一个 ZIP 包,里面包含多个 XML 文件(如 sheet1.xml)。FastExcel 直接生成这些 XML,避免了 POI 的对象模型开销。
2. 核心优势
特性 说明
✅ 零内存增长:数据逐行写入,不缓存到内存
✅ 无 POI 依赖:避免 POI 的复杂对象模型和内存泄漏
✅ 极致轻量:JAR 包仅 100KB+
✅ 支持流式响应可直接对接 HttpServletResponse.getOutputStream()
⚠️ 重要限制:FastExcel 仅支持写 .xlsx,不支持读取、不支持 .xls、不支持复杂样式。
三、Spring Boot 集成 FastExcel:完整实战
1. 添加 Maven 依赖
<dependency> <groupId>org.dhatim</groupId> <artifactId>fastexcel</artifactId> <version>0.15.2</version> <!-- 截至 2026 年最新版 --></dependency>
不需要 poi、easyexcel、commons-compress 等任何额外依赖!
2. 定义实体(无需注解)
FastExcel 不依赖注解,直接通过 worksheet.value(row, col, value) 写入。
public class Order { private Long id; private String orderNo; private BigDecimal amount; private LocalDateTime createTime; // getter/setter 略}
3. 控制器:分页 + 流式导出(生产级代码)
@RestControllerpublic class OrderExportController { @Autowired private OrderService orderService; @GetMapping("/export/orders") public void exportOrders(HttpServletResponse response) throws IOException { // 1. 设置响应头(支持中文文件名) String fileName = URLEncoder.encode("订单明细_" + LocalDate.now(), StandardCharsets.UTF_8); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx"); OutputStream os = response.getOutputStream(); // 2. 创建 FastExcel 工作簿 Workbook workbook = new Workbook(os, "订单数据", "1.0"); Worksheet sheet = workbook.newWorksheet("2025年订单"); // 3. 写表头 sheet.value(0, 0, "订单ID"); sheet.value(0, 1, "订单编号"); sheet.value(0, 2, "金额(元)"); sheet.value(0, 3, "下单时间"); // 4. 分页查询 + 流式写入 int pageSize = 10_000; int offset = 0; int rowIndex = 1; // 从第1行开始写数据(第0行是表头) while (true) { List<Order> batch = orderService.queryOrdersWithPagination(offset, pageSize); if (batch.isEmpty()) break; for (Order order : batch) { sheet.value(rowIndex, 0, order.getId()); sheet.value(rowIndex, 1, order.getOrderNo()); sheet.value(rowIndex, 2, order.getAmount().toString()); sheet.value(rowIndex, 3, order.getCreateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); rowIndex++; } offset += pageSize; } // 5. 关键!触发最终写入并关闭流 workbook.finish(); // 内部会 flush 并 close ZIP 输出流 os.flush(); }}
💡 关键点:
使用 offset + limit 分页,避免一次性加载千万数据到内存
workbook.finish() 必须调用,否则文件损坏!
四、性能实测:FastExcel vs EasyExcel
测试环境:
Spring Boot 3.2 + JDK 17
服务器:16GB RAM, 8 Core
数据量:10,000,000 条订单记录
结论:FastExcel 在大数据导出场景下,内存占用仅为 EasyExcel 的 2%!
五、FastExcel 的适用边界(什么情况下不能用?)
虽然 FastExcel 很强,但它不是万能的。以下场景请慎用:
📌 最佳实践:
报表类(带样式) → EasyExcel
日志/明细/对账类(纯数据) → FastExcel
六、工程建议:如何安全落地?
不要导出超过 100 万行的单个 Excel
Excel 单 sheet 最大行数为 1,048,576。超过此数应:
自动拆分为多个 sheet
或改用 CSV 格式(更轻量、兼容性更好)
异步导出 + 下载链接
对于超大任务,建议:
用户点击“导出” → 返回“任务已提交”
后台异步生成文件 → 上传 OSS
通过站内信/邮件通知下载链接
增加权限与频率控制
防止恶意用户频繁触发大数据导出,拖垮数据库。
七、结语:选对工具,比努力更重要
很多开发者习惯“一把梭哈”——不管什么场景都用 EasyExcel。
但真正的高手,懂得根据场景选择最合适的工具。
小数据、要样式?→ EasyExcel
大数据、纯文本?→ FastExcel
超大数据?→ CSV + 压缩 + 异步