Java Stream 流实现轻量级流式导出 Excel 的核心是数据库查询返回 Stream、分批拉取数据、EasyExcel 配合 OutputStream 分批写入且不缓存,全程避免大集合和对象重构造。

Java 中 Stream 流本身不是“直接写 Excel”的工具,它真正起作用的地方是串联数据源与写入动作,实现“不攒数据、不建大集合、不等全量”的轻量管道。关键不在 Stream 语法,而在用它约束数据流动节奏——让数据库查一行、转一行、写一行,全程不落地为 List。
数据库查询必须返回 Stream
不能把 findAll() 返回的 List 当作起点。Spring Data JPA 提供原生支持:
用 Stream<Order> findAllByStatus(String status) 直接获取流式结果;MyBatis 则需配合 @Options(fetchSize = 5000) + ResultHandler 或返回 Cursor<Order>。MySQL 要加 JDBC 参数 useCursorFetch=true&defaultFetchSize=5000,PostgreSQL 推荐游标分页(WHERE id > ? LIMIT N)。这样 ResultSet 不会一次性加载百万行进内存,而是按 fetchSize 分批拉取。
EasyExcel 写入必须走 doWrite + OutputStream
避免调用 write(List) 这种隐式缓存接口。正确姿势是:
- 用
EasyExcel.write(outputStream, clazz).build()构建 writer,outputStream 来自 HttpServletResponse 或 BufferedOutputStream - 每次只传一批数据(如 5000 条),调用
writer.write(dataBatch, sheet) - 不反复 new WriteSheet,复用同一个 WriteSheet 对象
- 最后必须
writer.finish(),否则 zip 流不闭合,文件损坏
中间处理禁止落地集合与重对象构造
Stream 的链式操作容易误用成“先 collect 再 map”,这等于又建了 List。应保持惰性:
立即学习“Java免费学习笔记(深入)”;
- 字段映射用数组或 record,不用 HashMap.put("key", obj.getVal())
- 时间格式统一在 DB 查询层转成字符串(如
DATE_FORMAT(create_time, '%Y-%m-%d %H:%i')),不依赖 Java 端 SimpleDateFormat - 脱敏、空值替换写成纯函数:
order -> new ExportRow(order.getId(), maskPhone(order.getPhone()), ...) - 禁用自动列宽、共享字符串表、冻结窗格等内存敏感特性
超大数据主动切 Sheet,不靠崩溃兜底
单 Sheet 行数上限是 1048576,但实际建议 90 万行就新建 Sheet:
- 每写满 90 万行,调用
writer.write(dataBatch, EasyExcel.writerSheet("Sheet" + (++sheetIndex)).build()) - 不要等 write 报错再处理,那是事后补救
- 分片逻辑放在循环内,和分页查询解耦:查 1 万条 → 写入当前 sheet → 检查行数是否超限 → 超则换 sheet


















