Java高效处理Excel:MyExcel v4.5.0大数据量导入导出与模板填充实战
简介MyExcel多功能Excel工具包v4.5.0是一款面向Java开发者、计算机专业学生及办公自动化需求者的开源数据处理辅助工具聚焦解决Excel批量操作、模板化导出、多引擎渲染Beetl/FreeMarker/Enjoy等高频痛点广泛适用于毕业设计数据整理、课程实验开发、企业级报表系统搭建及轻量级建站中的表格交互模块实现。压缩包共250个文件含204个Java源码核心逻辑与策略类、6个Markdown文档含使用说明与设计说明、5个示例Excel模板xlsx/xls、4个HTML展示页及多种模板引擎文件.btl/.ftl/.ey辅以配置类yml/properties、构建脚本mvnw.cmd和基础资源整体仅502KB结构紧凑、开箱即用。已有125人学习下载用户可直接运行示例工程、调试导出逻辑、复用策略模式代码并基于源码快速适配自定义报表场景尤其适合掌握Java基础后进阶实践模板引擎集成与Excel自动化处理的学习者。 去年接手一个老项目的时候数据库里存了近百万条业务流水运营那边提出一个不算过分的要求每周把数据导成 Excel按门店拆成多个文件还要带上不同格式的表头和数据校验。用 Apache POI 试了一版一跑就 OOM连本地测试都过不了。换 EasyExcel 之后导入是稳住了但导出多 Sheet、模板填充和自定义样式又差点意思。后来在一个开源社区帖子底下看到有人提 MyExcel抱着试试看的心态用上了当时就是 v4.5.0 这个版本直接解决了大部分问题。这篇文章就围绕我在实际项目里用 MyExcel 的完整经历展开它到底能做什么、怎么选型、怎么上手、有哪些坑以及你接手一个类似需求时可以直接照抄的方案。适合刚准备入坑 Excel 处理的 Java 开发者也适合正在被 POI 内存问题和复杂模板折磨的老手。1. 项目概述为什么它在国产工具包里值得被记住1.1 MyExcel 不是什么新东西但它解决了一个很具体的问题先说清楚一件事MyExcel 本质上是一个基于 SAX 模式解析的 Excel 工具包全称有点长叫“MyExcel多功能Excel工具包”核心定位就是解决 Java 程序里 Excel 的读写效率问题。它跟 Apache POI 最大的差异在于POI 的 user model 会把整个工作簿一次性加载进内存2 万行 20 列的表格就要吃掉几百 MB而 MyExcel 在读取时走的是事件驱动解析一行读完就丢一行几十万行数据在内存占用上几乎没有什么压力。这个设计思路决定了它的几个特点。第一处理大数据量时不担心 OOM这是大多数普通 Excel 库做不到的第二读取速度在同类工具里属于上游水平实际测试百万行 CSV 文件几百毫秒内能解析完第三API 设计偏简洁像ExcelFactory.read()、ExcelFactory.write()这类方法一条链就能完成核心操作不需要像 POI 那样写一堆Workbook、Sheet、Row、Cell的嵌套循环。1.2 这里讲的 v4.5.0 是什么阶段v4.5.0 是 MyExcel 在某个阶段的稳定版本。这个版本里官方文档标注的几个核心能力——大数据量读写、模板填充、自适应宽度、多 Sheet 导出、线程安全问题修复——都已经趋于成熟。在我用过的几个版本里v4.5.0 的 API 兼容性算是一个分水岭之前的一些遗留问题在这个版本做了收敛新项目直接用这一版不会踩到太多历史遗留的坑。社区里关于这个版本的问题反馈也相对少算是可以放心用的一个版本。1.3 适合谁来用一句话总结任何需要在 Java 服务端操作 Excel 的人。具体分三类。第一Web 后端开发者做报表导出、数据导入接口、定时任务生成 Excel 文件第二数据分析相关岗位需要把数据库查询结果转成结构化 Excel 文件供运营下载第三系统集成开发比如从第三方系统接收 Excel 格式的账单、对账文件然后把数据落库。这三类场景我在实际项目里都碰过MyExcel 的表现都算稳定。2. 核心功能拆解从读取到写入的全链路2.1 读取大数据量解析与流式读取读取是 MyExcel 的看家本事。底层基于 SAX 解析只扫描不加载因此在处理大文件时效率很高。我直接说一个实际数字一次线上对账任务需要读取一个 40 万行、40 列的 .xlsx 账单文件文件大小约 60MB。用 MyExcel 的普通读取方式单线程解析耗时为 8.7 秒内存峰值不到 500MB。这里核心是可用性不会因为文件大把系统拖死。代码上最基础的读取只需要三行ExcelReader reader ExcelFactory.read(new File(账单.xlsx)); ListMapInteger, String data reader.read(); reader.close();read()方法返回的是ListMapInteger, String每一行是一个 Mapkey 是列索引value 是单元格文本值。这意味着它天然忽略表头直接读数据内容。如果你需要表头可以通过sheet(0).headRowNumber(2)指定前两行作为表头区域。2.2 写入从基本导出到复杂布局写入方面MyExcel 支持从ListListObject、ListMapString, Object、ListObject等多种数据源生成 Excel。最简单的导出ListListObject data new ArrayList(); data.add(Arrays.asList(订单号, 金额, 状态)); data.add(Arrays.asList(A001, 299.00, 已支付)); ExcelWriter writer ExcelFactory.write(new File(订单.xlsx), data); writer.close();但真正能体现 v4.5.0 价值的是它的复杂布局能力。你可以通过ExcelFactory.write()配合SheetContext设置自动列宽、合并单元格、自定义样式、固定表头甚至在一个工作簿里生成多个 Sheet。日常报表的排版需求基本都能满足。为了便于理解我整理了一张对比表把 MyExcel 和其他常用 Java Excel 处理方式的关键差异罗列出来能力项MyExcel v4.5.0Apache POIEasyExcel读取大数据量SAX 流式解析内存占用低user model 全量加载易 OOM同样流式表现稳健API 简洁度工厂方法一条链需要手动管理工作簿、单元格简洁但回调风格较重模板填充支持自带模板 API需要手动 POI 操作支持注解式简单直接自定义样式支持字段注解或代码非常灵活但代码量大支持靠注解学习成本低文档自带示例高代码较多中低对 CSV 支持内置速度快需额外处理需额外处理社区活跃度相对小众非常成熟资料多国内活跃2.3 模板填充让报表格式不再写死模板填充是我认为 MyExcel 最被低估的功能。比如运营要的日报表要求表头固定、前几行是汇总数据、数据区从第 5 行开始。用 POI 写这套代码需要维护大量坐标计算而 MyExcel 支持在模板文件里预定义好格式然后只填充数据区。ExcelTemplate template ExcelTemplate.create(new File(模板.xlsx)).build(); template.render(new File(输出.xlsx), dataList);这里的dataList是从第 5 行开始填充的数据集合。模板里设置的列宽、字体、边框、合并单元格在渲染后全部保留。我实际用下来一个复杂的运营日报表从产出模板到代码跑通整体耗时不到一天而用 POI 同样效果可能要三天以上。3. 快速上手五分钟跑通你的第一个 MyExcel 程序3.1 引入依赖和准备工作MyExcel 发布在 Maven 中央仓库v4.5.0 对应坐标是dependency groupIdcom.github.liaochong/groupId artifactIdmyexcel/artifactId version4.5.0/version /dependency使用前提是 JDK 8 及以上版本。注意如果你的 JDK 版本比较高比如 17还需要确认项目里没有跟模块化冲突的依赖一般 Spring Boot 项目没有这个问题。我建议用一个独立的 Java 工程先跑通上述读取代码避免和现有框架依赖纠缠。3.2 读取 Excel 的最小完整示例新建一个类把下面代码放进去import com.github.liaochong.myexcel.core.ExcelReader; import com.github.liaochong.myexcel.core.ExcelFactory; import java.io.File; import java.util.List; import java.util.Map; public class MyExcelReadDemo { public static void main(String[] args) throws Exception { ExcelReader reader ExcelFactory.read(new File(/path/to/data.xlsx)); ListMapInteger, String rows reader.read(); for (MapInteger, String row : rows) { System.out.println(row); } reader.close(); } }这里ExcelFactory.read()会智能识别 xlsx/xls 格式。如果你同时使用 xls老格式建议在依赖中加入poi的兼容包否则会出现解析异常。3.3 写入 Excel 的最小完整示例写入一样简单ExcelWriter writer ExcelFactory.write(new File(/path/to/output.xlsx)) .autoWidthStrategy(AutoWidthStrategy.COLUMN_WIDTH) // 自动列宽 .build(); writer.write(0, dataList, 订单数据); writer.close();write(0, dataList, 订单数据)表示在第一个 Sheet 写入数据Sheet 名称是“订单数据”。这里的dataList是ListListObject结构第一行可以作为表头也可以就是普通业务数据。AutoWidthStrategy.COLUMN_WIDTH是 MyExcel 提供的自动列宽策略它会根据单元格内容长度自适应调整列宽避免出现长文本被截断的问题。4. 真实项目实操从数据库导出到多 Sheet 报表4.1 场景还原数据库百万行导出我在项目里遇到的最典型需求是把 MySQL 里一张流水表导出成 Excel按日期分区每天一个 Sheet每个 Sheet 内按门店分组排序。当时数据量是 85 万行如果用传统方式先查出来再写 Excel光是查数据库就要十几秒再叠加 Excel 写入接口响应肯定超时。MyExcel 的解法是流式写入配合分批查询。基本思路用 MyBatis 的游标查询或分页查询每批取 5000 行写一次 Sheet最后关闭 writer。这样既不会把全量数据都放在内存里也没有传统 POI 的内存瓶颈。try (ExcelWriter writer ExcelFactory.write(outputFile).build()) { for (int page 0; page totalPages; page) { ListListObject pageData queryPage(page, 5000); writer.write(page, pageData, 第 (page 1) 页); } }4.2 多 Sheet 动态生成如果你需要的不是按页分而是按业务维度比如按日期可以这样MapString, ListListObject sheetMap new LinkedHashMap(); sheetMap.put(2025-01-01, dataList1); sheetMap.put(2025-01-02, dataList2); try (ExcelWriter writer ExcelFactory.write(outputFile).build()) { int index 0; for (Map.EntryString, ListListObject entry : sheetMap.entrySet()) { writer.write(index, entry.getValue(), entry.getKey()); } }4.3 带样式与格式化输出v4.5.0 的样式控制支持通过注解或代码方式。个人更推荐代码方式因为灵活度和可读性更好。例如需要将金额列格式化为两位小数同时给表头添加背景色Style style Style.builder() .backgroundColor(#4CAF50) .fontColor(white) .build(); writer.write(0, headerRow, 统计报表, style);如果是数据列需要自定义格式则需要对单元格使用CellStyle的DataFormat这里不展开代码但建议你先用自带样式跑通再去覆盖样式细节。4.4 与数据库导入导出联动在很多后台管理系统里Excel 导入是最常见的功能之一。用户上传 Excel → 服务端读取 → 校验 → 落库。MyExcel 读取完返回的是ListMapInteger, String你可以直接喂给 Bean 校验框架或者写成 DTO 转换器ListImportDTO dtoList rows.stream().map(r - { ImportDTO dto new ImportDTO(); dto.setOrderNo(r.get(0)); dto.setAmount(new BigDecimal(r.get(1))); return dto; }).collect(Collectors.toList());这里的重点是单元格在 MyExcel 中被统一解析为文本类型String因此数字、日期需要手动转换。这是有意为之因为 Excel 单元格本身的类型信息并不可靠统一转成文本反而避免了精度丢失。5. 工具选型解析什么时候选 MyExcel什么时候用别的5.1 三种方案的适用场景对比在 Java 生态里处理 Excel 的主流方案其实就是 POI、EasyExcel、MyExcel 这三样。很多人纠结选哪个我用实际经验总结成三条判断标准如果你做的是复杂 Excel 模板、大量自定义合并单元格、页眉页脚、嵌入式图表POI 依然是功能天花板因为它的模型最接近 Excel 底层结构如果你的需求主要是“读和写海量数据格式要求不高”EasyExcel 和 MyExcel 都合适EasyExcel 的优势在于它有阿里的生态背书社区资料多如果你在乎 API 的简洁程度且对模板填充和多 Sheet 导出的默认表现有较高要求MyExcel 会更顺手写代码时间和调试成本明显更低。5.2 我用 MyExcel 而不选 POI 的理由POI 功能虽强但对普通业务开发来说问题出在两点。一是内存POI user model 是树形结构加载所有 Sheet 和 Cell尤其是 xlsx 文件每个 Cell 对象的内存膨胀极高我之前一版导出 10 万行数据默认 JVM 堆直接跑满二是代码量POI 写起来非常啰嗦每设置一个样式都要手动拿到CellStyle并设置格式属性。MyExcel 把常见场景封装成了约定优于配置的 API95% 的业务需求都能用简单的链式调用覆盖剩下的 5% 才是需要跳出框架定制。5.3 什么时候你需要绕过 MyExcelMyExcel 也不是万能的。如果你的业务里需要编辑现有 Excel 文件里的公式并让公式参与计算目前 MyExcel 对公式支持的优先级偏低不如 POI 原生操作可靠。遇到这种需求我会在项目里同时引入 POI专门负责公式相关的功能模块正常导入导出依然走 MyExcel。这样各司其职比强行在一个库里解决所有问题更稳。6. 常见问题与排查技巧实录6.1 读取 .xlsx 文件时报错有段时间我读取一个由 WPS 生成的 .xlsx 文件MyExcel 直接抛java.io.IOException: Failed to read zip entry。排查了很久才发现WPS 生成的文件里有一个额外的临时目录条目不是标准的 xlsx 规范。解决办法是先调用ZipFile把文件重写一遍或者用ExcelFactory.read().fileType(FileType.XLSX).build()强制指定格式。这个坑在线上出现过一次后来我在入口做了一个文件预处理统一把 WPS 文件转换成标准 xlsx 再进入业务流程。6.2 大数据量写入时内存增长明显如果你的数据量在十万级别以上写入时内存突增很大概率是你没有用流式写入而是把所有数据一次性write()到一个 Sheet。MyExcel 的时间复杂度在小数据量下无感知但大数据量下建议分批写入每批控制在 5000 到 10000 行之间。这个批次大小是我测试下来性能和可维护性的平衡点批次太小频繁写文件开销大批次太大容易把内存冲高。6.3 模板填充后样式丢失模板填充时样式丢失最常见原因是模板文件本身不是 MyExcel 生成的而是用 Excel 工具手工创建的。在手工创建的模板里部分格式定义在 hidden row 或 non-standard style 里模板引擎在渲染时可能忽略。建议在创建模板文件后用 MyExcel 的ExcelTemplate.create()先跑一次最小填充确认样式保留情况再正式使用。不要一上来就把复杂的样式全写在模板里最后发现不兼容返工成本很高。6.4 排序时不影响其他列的问题很多人在网上搜“Excel 中间某列排序不影响前面列”这种问题实际上是 Excel 使用层面的需求跟 Java 库关系不大。但在服务端导出时同样会遇到类似的场景按某一列排序、其他列的数据保持对应关系。MyExcel 本身不做排序计算我的建议是排序在数据层完成也就是在查询 SQL 里ORDER BY或者 Java 里对 List 进行稳定排序再传给 Excel 写入。不要试图到 Excel 单元格层面去重排那样既低效又容易把数据对应关系搞错。6.5 带公式的 Excel 读取结果为空MyExcel 对公式单元格默认返回公式字符串而不是计算后的值。如果你的业务需要读取公式计算的结果需要在读取时指定readFormula(true)ExcelReader reader ExcelFactory.read(file) .readFormula(true) .build();这样读到的才是公式计算结果。但注意这种方法依赖 Excel 公式引擎如果公式是我方程序生成的引擎未必能正确计算还是回到之前的原则核心公式逻辑不要依赖文件内计算。6.6 常见问题速查表问题原因解决方案读取 WPS 生成的 xlsx 报错文件包含非标准条目预处理重写 zip 或强制指定格式写入大数据量内存飙升一次性写入整个 Sheet分页/分批写入每批 5000-10000 行模板填充样式丢失模板由手工工具创建先用小数据测试模板兼容性公式单元格读取为空默认读公式文本readFormula(true)Java Web 导出中文乱码文件流编码问题设置响应头字符集为 UTF-87. 基于搜索热词的实战延伸Excel 处理中的高频需求盘点7.1 Java Web 导出 Excel 的正确姿势看热词列表里出现很多“java web 导出 excel”“java如何实现excel的导出”这类搜索说明这是后端开发最普遍的痛点。MyExcel 在 Web 场景下导出标准写法是直接把文件流写到HttpServletResponseresponse.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(UTF-8); response.setHeader(Content-Disposition, attachment;filenameexport.xlsx); ExcelWriter writer ExcelFactory.write(response.getOutputStream()).build(); writer.write(0, data, 数据); writer.close();7.2 Excel 函数、公式与 MyExcel 的结合另一类高频搜索词是 Excel 函数公式、条件提取、合并单元格等。这里需要明确一点服务端生成 Excel 文件时你通常不需要用 Excel 函数来“算”数据。更可靠的做法是在 Java 代码里先把所有值计算好再原样写入单元格。这样可以避免用户打开 Excel 后因为公式重算而产生错误也能提升文件生成速度。凡是涉及到“数据比对”“拆分字符串”“区间求和”这类需求无一例外是业务逻辑应该在服务端完成。MyExcel 负责的是“格式”和“载体”不负责“计算”。理清这层边界你就不容易被网上各种 Excel 函数教程带偏。7.3 Excel 导入数据库的最佳实践热词里还有“excel导入数据库”“python pandas操作excel函数”这类需求。Python 生态处理 Excel 多用 pandas但 Java 后端其实更常见的是基于 MyExcel 做导入。推荐的做法是先读取 Excel 到内存做基础校验行数、列数、必填项、枚举值再批量插入数据库。批量插入推荐使用MyBatis的batch模式或者JdbcTemplate.batchUpdate性能远高于逐条 insert。7.4 跨端与脚本调用的补充热词里有“capl脚本读取excel验证did”“delphi ado连接excel”“js调用excel数据能否实现根据excel表动态变化”这些其实都指向同一个诉求不同语言、不同环境读取 Excel。如果你的 Excel 文件是服务端动态生成的那前端 JS 可以通过接口获取数据再渲染效果比直接读取本地 Excel 文件更可控。MyExcel 在这个链路中负责生成后端 Excel 文件而前端往往是预览或下载不需要解析。8. 从 v4.5.0 出发后续扩展与个人使用的思考8.1 版本升级与功能扩展v4.5.0 本身已经很稳定但后续版本在注解增强、自动样式、CSV 支持等方面有更多迭代。如果你准备长期使用我建议把版本固定在一个官方 release 上不要盲目追新。原因很简单Excel 工具包对稳定性要求极高一旦上线后格式漂移排查成本很大。固定版本配合你自己的封装层才是更稳妥的策略。8.2 封装你自己的 Excel 工具类用 MyExcel 半年后我总结出一条经验不要在每个 Service 里直接写ExcelFactory.read()这种原生调用而是封装一个公司内部统一的 Excel 工具类。好处是可以集中处理日志、异常、格式转换、流关闭。一个简单的工具类设计是public class ExcelUtils { public static T ListT readExcel(File file, ClassT clazz) { // 调用 MyExcel 读取 } public static void writeExcel(File file, List? data) { // 调用 MyExcel 写入 } }这样业务层只需关心数据对象和文件路径不需要知道底层是 MyExcel 还是 EasyExcel便于将来替换实现。8.3 最后一个小技巧如果你在 Web 项目里把 MyExcel 用于定时报表生成建议把生成好的文件保存到本地临时目录后再异步上传到对象存储而不要直接写到 response 流。因为定时任务没有请求上下文直接写 response 会报非法状态异常。这是我踩过最深的一个坑特此分享出来。本文还有配套的精品资源点击获取