资讯动态

EasyExcel自定义样式策略:不同列不同样式实现与性能优化

发布时间:2026/9/13 1:56:42 来源:尧图企业网站定制
做 Java 后端的兄弟十有八九都遇到过导出 Excel 被业务方挑刺的情况数据没问题但表格一点样式都没有白底黑字、无边框、无对齐打印出来像一摞草稿纸。我第一次用 EasyExcel 的时候也纳闷明明这个库把 POI 封装得那么顺手为什么默认导出的文件这么素后来查源码才想明白样式本来就不是 EasyExcel 的默认行为写不写样式全看你有没有注册对应的样式策略。这篇我就拿实际项目里不同列要不同样式的需求来拆讲清楚自定义样式策略怎么设计、怎么写、有哪些坑以及 EasyExcel 一些高频问题复杂表头、模板填充、单元格换行、版本冲突该怎么排查。看完你至少能写出一个属于自己的通用样式处理器放进项目里直接复用。1. 需求解析你到底需要什么样的样式效果1.1 先别写代码把样式需求拆成清单很多人在 EasyExcel 里写样式翻车不是因为 API 不会用而是需求根本没想透。给表格加样式这句话太模糊了落到具体 Excel 文件里至少包含这些维度字体、字号、颜色、背景色、对齐方式、是否自动换行、边框、行高列宽、数字格式。再加上作用范围又分为表头行、数据行、整表、某一列、某个单元格。所以我接到需求的第一件事是先把要什么样式翻译成一张清单。比如我们最近导出的一份订单报表需求大概是这样的位置样式要求表头整行深蓝背景、白色加粗字体、居中、带细边框序号列第1列数据居中、不加粗订单金额列第4列数据右对齐、带千分位格式、字体红色状态列第5列根据值变背景色成功绿、失败红、处理中黄备注列第6列自动换行、左对齐、上下居中其他数据列默认样式加细边框、垂直居中这张表看起来不难但用 EasyExcel 默认能力去套就会发现内置的HorizontalCellStyleStrategy只能做到表头一套样式、数据区一套样式根本做不到不同列不同规则更别提某一列根据单元格值动态变色。这就是我们非要自定义样式策略的根本原因。1.2 选型思路先看自己属于哪种场景我总结下来EasyExcel 样式需求一般就三种量级不同量级方案完全不同全表统一只要表头加个粗、数据加个边框那用HorizontalCellStyleStrategy就够了不需要自己动脑。按固定规则区分列比如某几列右对齐、某几列加背景色、某列自动换行这些可以用CellWriteHandler在单元格写入后动态处理。完全动态列数不固定、样式根据数据值变化、甚至导出格式都由用户配置这种最复杂基本就得自己封装一套规则引擎本质还是围绕CellWriteHandler和WriteCellData做扩展。其实日常 90% 的需求都属于第二种也就是 固定列 差异化样式。这篇文章重点讲的也是这种因为它是性价比最高的做法代码量不大又能覆盖绝大多数业务场景。2. 核心机制EasyExcel 样式扩展的运行原理2.1 从 POI 到 EasyExcel样式到底是在哪个环节写进去的要搞懂自定义样式策略得先理解 EasyExcel 写出 Excel 的过程。底层还是 Apache POI 在干活EasyExcel 做的事情是在 POI 之上加了一层事件回调机制你写的各种 Handler 就像是生产线上的质检工在单元格创建、写入、样式处理等节点被回调。其中最关键的一个接口是CellWriteHandler。它在写单元格的整个生命周期里提供了几个钩子方法beforeCellCreate单元格创建前、afterCellCreate单元格创建后、afterCellDispose单元格数据处理完毕等。我们要改样式通常在afterCellDispose里动手因为到这个阶段单元格的值已经写进去了样式也处于马上要落盘的状态你这时候改就是最终效果。接口签名大致长这样public interface CellWriteHandler extends WriteHandler { default void afterCellDispose(WriteCellData? writeCellData, WriteSheetHolder writeSheetHolder, WriteRowHolder writeRowHolder, WriteCellHolder writeCellHolder) { } }注意不同版本签名可能有差异新版还引入了带WriteHandlerContext的重载方法但基本逻辑一致我用的是 3.x 里最常见的五参写法如果你的 IDE 提示过时或者找不到看下依赖版本里的默认方法签名就行。我们实现的 handler 注册到EasyExcel.write()的链路上每一行每一个单元格处理后都会被回调。2.2 回调里能拿到什么这和样式有什么关系afterCellDispose这五个参数每个都有用writeCellData是当前单元格的数据和样式载体writeSheetHolder可以拿到当前 SheetwriteRowHolder能拿到当前行的信息writeCellHolder能拿到单元格的行号列号、是否为表头等元信息。这时候就可以做判断了。我想给不同的列设置不同的样式核心就是通过writeCellHolder.getColumnIndex()拿到列索引然后针对性地往writeCellData.getOrCreateStyle()返回的WriteCellStyle里塞样式属性。比如我要把某一列背景色设成浅绿代码是这样WriteCellStyle cellStyle writeCellData.getOrCreateStyle(); cellStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex()); cellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);WriteCellStyle里能设置的东西很多字体用setWriteFont()传入WriteFont对象边框用setBorderLeft()、setBorderBottom()等方法对齐用setHorizontalAlignment()、setVerticalAlignment()换行用setWrapped(true)数字格式用setDataFormat()。这些属性写起来和 POI 很像但它是 EasyExcel 自己的元数据对象最终由 EasyExcel 在写文件时才真正转成 POI 的CellStyle。2.3 一个容易被忽略的重点别直接去 new POI 的 CellStyle这里有个性能大坑。很多新手在 handler 里图省事直接拿原生 POI 的方式搞Cell cell writeCellHolder.getCell(); CellStyle style writeSheetHolder.getSheet().getWorkbook().createCellStyle(); style.set...; cell.setCellStyle(style);问题是一个 Excel 文件里的 CellStyle 是有数量上限的老的.xlsHSSFWorkbook上限是 4000 个左右.xlsxXSSFWorkbook虽然高一些但也架不住你在回调里几十万行每行都 new 一个。跑大数据量导出的时候要么内存爆了要么文件打不开要么导出速度慢到怀疑人生。EasyExcel 提供的WriteCellStylegetOrCreateStyle()是经过复用的设计真正落到文件时 EasyExcel 会帮你做样式的合并和复用所以能用它的方式就别自己造轮子。这也是自定义样式策略里最值得记住的一条原则。3. 实操篇不同列设置不同样式的三种实现方式3.1 方式一直接实现 CellWriteHandler按列索引分发样式这是最直观、最灵活的方式也是我项目里一直在用的方式。核心思路很朴素在afterCellDispose里拿列号然后走一个 switch 或者 Map 分发到不同的样式方法里。我先把一个通用一点的 handler 骨架写出来import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteCellHolder; import com.alibaba.excel.write.metadata.holder.WriteRowHolder; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.VerticalAlignment; public class MultiColumnStyleHandler implements CellWriteHandler { Override public void afterCellDispose(WriteCellData? writeCellData, WriteSheetHolder writeSheetHolder, WriteRowHolder writeRowHolder, WriteCellHolder writeCellHolder) { Integer rowIndex writeCellHolder.getRowIndex(); Integer columnIndex writeCellHolder.getColumnIndex(); if (rowIndex null || columnIndex null) { return; } // 表头单独处理这里假设只有一行表头 if (rowIndex 0) { applyHeadStyle(writeCellData); return; } switch (columnIndex) { case 0: applySeqStyle(writeCellData); break; case 3: applyAmountStyle(writeCellData); break; case 4: applyStatusStyle(writeCellData); break; case 5: applyRemarkStyle(writeCellData); break; default: applyDefaultStyle(writeCellData); break; } } private void applyHeadStyle(WriteCellData? writeCellData) { WriteCellStyle style writeCellData.getOrCreateStyle(); style.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex()); style.setFillPatternType(FillPatternType.SOLID_FOREGROUND); style.setHorizontalAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); WriteFont font new WriteFont(); font.setBold(true); font.setColor(IndexedColors.WHITE.getIndex()); font.setFontHeightInPoints((short) 12); style.setWriteFont(font); setBorder(style); } private void applySeqStyle(WriteCellData? writeCellData) { WriteCellStyle style writeCellData.getOrCreateStyle(); style.setHorizontalAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); } private void applyAmountStyle(WriteCellData? writeCellData) { WriteCellStyle style writeCellData.getOrCreateStyle(); style.setHorizontalAlignment(HorizontalAlignment.RIGHT); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setDataFormat((short) 3); // 0.00_ 千分位保留两位小数 WriteFont font new WriteFont(); font.setColor(IndexedColors.RED.getIndex()); style.setWriteFont(font); } private void applyStatusStyle(WriteCellData? writeCellData) { // 根据值动态设置背景色 String status writeCellData.getStringValue(); WriteCellStyle style writeCellData.getOrCreateStyle(); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setHorizontalAlignment(HorizontalAlignment.CENTER); if (status null) { return; } switch (status.trim()) { case 成功: style.setFillForegroundColor(IndexedColors.SEA_GREEN.getIndex()); style.setFillPatternType(FillPatternType.SOLID_FOREGROUND); break; case 失败: style.setFillForegroundColor(IndexedColors.ROSE.getIndex()); style.setFillPatternType(FillPatternType.SOLID_FOREGROUND); break; case 处理中: style.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex()); style.setFillPatternType(FillPatternType.SOLID_FOREGROUND); break; default: break; } } private void applyRemarkStyle(WriteCellData? writeCellData) { WriteCellStyle style writeCellData.getOrCreateStyle(); style.setWrapped(true); style.setHorizontalAlignment(HorizontalAlignment.LEFT); style.setVerticalAlignment(VerticalAlignment.CENTER); } private void applyDefaultStyle(WriteCellData? writeCellData) { WriteCellStyle style writeCellData.getOrCreateStyle(); style.setVerticalAlignment(VerticalAlignment.CENTER); setBorder(style); } private void setBorder(WriteCellStyle style) { style.setBorderLeft(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN); style.setBorderTop(BorderStyle.THIN); style.setBorderBottom(BorderStyle.THIN); } }使用的时候只要在导出链路上注册它EasyExcel.write(response.getOutputStream(), OrderExportBO.class) .registerWriteHandler(new MultiColumnStyleHandler()) .sheet(订单报表) .doWrite(dataList);这种方式的好处是直白你完全掌控每个单元格的样式逻辑。缺点是如果列特别多、规则特别复杂这个类会膨胀得很厉害。但没关系至少我们先跑通了后面再优化也不迟。3.2 方式二继承 AbstractCellStyleStrategy做一个真正的策略EasyExcel 在com.alibaba.excel.write.style包下提供了一个抽象类AbstractCellStyleStrategy。看名字就知道这其实就是官方为策略模式准备的基类。它内部会在写表头和数据行时分别调用两个抽象方法setHeadCellStyle和setContentCellStyle。public abstract class AbstractCellStyleStrategy implements WriteHandler { protected abstract void setHeadCellStyle(CellStyle headCellStyle, ListWriteCellData? headCellDataList, WriteSheetHolder writeSheetHolder, WriteSheet writeSheet); protected abstract void setContentCellStyle(CellStyle contentCellStyle, WriteCellData? cellData, WriteSheetHolder writeSheetHolder); }这里有个关键差异它的setContentCellStyle拿到的contentCellStyle是整个数据行共用的一个样式对象不是每个单元格独立。所以按列区分不同样式要么你在方法里根据cellData的列信息动态 clone 出多个样式要么就只在全数据区统一一种样式的场景用。我个人的经验是这个类适合做所有数据行同一套基础样式 少量特例的场景比如统一加边框、统一行高、统一对齐方式。真到了按列花式变样式还是直接实现CellWriteHandler更顺手因为它给你的是完整的回调上下文不用在共用样式对象上绕弯子。3.3 方式三注解驱动的轻量方案EasyExcel 还提供了一些注解可以声明式地给某个字段设置样式比如ContentStyle、ContentFontStyle、HeadStyle、HeadFontStyle、ColumnWidth、ContentRowHeight等。用法就是在导出实体类的字段上加注解Data public class ProductBO { ExcelProperty(商品名称) ColumnWidth(20) private String name; ExcelProperty(价格) ContentStyle(horizontalAlignment HorizontalAlignment.RIGHT) ContentFontStyle(color 10) private BigDecimal price; }这种方式最快适合样式规则固定、不需要动态判断的简单场景。但它的缺点也很明显每个字段的注解是写死的你想根据值变色做不到想表头和数据行用不同规则也比较繁琐而且注解多了以后实体类会显得非常臃肿。所以我一般只在内部快速演示、或者模板相对固定的导出场景用。生产环境里复杂报表我还是推荐方案一。4. 实战案例完整导出订单报表并应用自定义样式策略4.1 需求场景与数据结构纸上谈兵没意思我拿一个真实项目里的订单报表导出场景来完整走一遍。需求就是上面提到的表头深蓝背景、白色加粗、居中、细边框。序号列居中。订单金额列右对齐、千分位格式、红色字体。状态列成功显示绿色背景、失败显示红色背景、处理中显示黄色背景。备注列自动换行、左对齐。其余数据列加细边框、垂直居中。实体类我定义为OrderExportBO导出字段包括序号、订单号、客户名称、订单金额、状态、备注六个字段。注意金额列我期望输出千分位格式所以我直接在实体类上用NumberFormat注解来格式化数据样式上再配合右对齐避免在样式里处理数字格式。Data public class OrderExportBO { ExcelProperty(value 序号) private Integer seq; ExcelProperty(value 订单号) private String orderNo; ExcelProperty(value 客户名称) private String customerName; ExcelProperty(value 订单金额) NumberFormat(0.00) private BigDecimal amount; ExcelProperty(value 状态) private String status; ExcelProperty(value 备注) private String remark; }注意我这里故意没有在实体类上堆样式注解因为样式规则比较多用注解会让实体类很难看而且状态列需要动态变色注解做不了。全部交给自定义 handler 统一处理逻辑集中后面也好维护。4.2 导出服务与 Controller 的完整代码有了实体类我把导出的 Service 方法写出来Service public class OrderExportService { public void exportOrderReport(LocalDate startDate, LocalDate endDate, HttpServletResponse response) throws IOException { // 实际业务中这里从数据库分页查询这里用示例数据代替 ListOrderExportBO dataList buildDemoData(); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(订单报表, UTF-8).replaceAll(\\, %20); response.setHeader(Content-disposition, attachment;filename*utf-8 fileName .xlsx); try (ExcelWriter excelWriter EasyExcel.write(response.getOutputStream(), OrderExportBO.class) .registerWriteHandler(new MultiColumnStyleHandler()) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .build()) { WriteSheet writeSheet EasyExcel.writerSheet(订单报表).build(); excelWriter.write(dataList, writeSheet); } } private ListOrderExportBO buildDemoData() { ListOrderExportBO list new ArrayList(); for (int i 1; i 20; i) { OrderExportBO bo new OrderExportBO(); bo.setSeq(i); bo.setOrderNo(SO20250101 String.format(%04d, i)); bo.setCustomerName(客户 i); bo.setAmount(new BigDecimal(100.00).multiply(BigDecimal.valueOf(i))); if (i % 3 0) { bo.setStatus(失败); } else if (i % 2 0) { bo.setStatus(处理中); } else { bo.setStatus(成功); } bo.setRemark(i % 4 0 ? 订单备注较长需要自动换行显示完整信息 : ); list.add(bo); } return list; } }Controller 层很简单就是接收查询参数调用 Service把响应对象传进去RestController RequestMapping(/order) public class OrderExportController { Resource private OrderExportService orderExportService; GetMapping(/export) public void export(RequestParam(required false) LocalDate startDate, RequestParam(required false) LocalDate endDate, HttpServletResponse response) throws IOException { orderExportService.exportOrderReport(startDate, endDate, response); } }这里有几个细节值得说LongestMatchColumnWidthStrategy是 EasyExcel 内置的自动列宽策略能让列宽根据内容长短自适应算是一个低成本提升导出效果的小技巧搭配我们的自定义样式 handler 用很舒服。另外导出文件名的编码处理我用了URLEncoder是为了防止中文文件名在浏览器里乱码。如果你们公司有统一的下载网关这段可以省略。4.3 效果验证与检查点代码写完启动服务访问导出接口打开生成的 Excel 文件挨个检查清单里的样式是否生效表头第一行是不是深蓝底、白字加粗如果不是先检查rowIndex 0的判断是否被多行表头影响。金额列是不是右对齐、有没有千分位注意我这里用NumberFormat(0.00)格式化数据样式里的setDataFormat就没有和它冲突否则两者会打架。状态列背景色是否按值变化比如失败的行状态单元格应该是红色背景。如果所有状态都是默认背景多半是writeCellData.getStringValue()取值没取到可以改用writeCellHolder.getCell()读取。备注列超长文本是否换行换行除了样式设置setWrapped(true)还有个前提是行高足够否则内容被截断看不到完整文字。所以如果换行没生效可以顺便设置一个合适的行高见后面的问题排查。我实测下来这套代码导出 20 行、几万行的数据都能稳定跑样式表现一致也不存在样式互相串的问题。5. 高频问题与排查实录5.1 复杂表头时列索引对不上自定义样式策略最常见的一个坑就是表头不是简单的一行。比如表头带合并单元格、多级表头动态表头由 List 拼接这时rowIndex不再只是 0而可能是 0、1、2 多行更坑的是如果使用了某些表头合并策略列索引columnIndex不一定和你数据库字段顺序一一对应容易把样式加到错误的列上。我的排查思路是先别急着写样式在 handler 里临时打印rowIndex和columnIndex和实际文件里的单元格位置对照一遍。如果是动态表头最好先把表头文本读出来拿表头文本做映射而不是硬编码列索引。比如在afterCellDispose里如果判断当前是表头行就通过writeSheetHolder.getSheet().getRow(rowIndex).getCell(columnIndex).getStringCellValue()把表头文本取出来存成一个MapString, Integer后续数据行再根据表头文本查列号这样就算前端动态加列、调整顺序样式策略也不会乱。5.2 模板填充模式fill下样式策略不生效很多人在用EasyExcel.fill()做模板填充时发现自己注册的CellWriteHandler根本没被调用或者调了也没效果。这不是代码写错了而是 fill 模式的写入逻辑和普通 write 模式不一样fill 模式下 EasyExcel 只是把数据塞进模板里预先留好的单元格单元格的样式早就在模板文件里定好了EasyExcel 不会去重建。换句话说这个模式下的样式是模板负责的不是代码负责的。解决办法有两个第一模板文件里把要填充的单元格样式提前设置到位这是最推荐的做法第二如果你确实需要代码动态控制样式就别用 fill 模式改成普通 write 模式自己拼表头。我之前有次接手一个别人的导出接口他用 fill 模式填数据又想给状态列变色折腾了半天才发现方向错了——模板里根本没有给状态列预留任何可变的样式扩展点。5.3 单元格自动换行不生效单元格换行是导出需求里的高频点尤其是备注、地址这类长文本。样式层面要做的就一件事给对应列设置setWrapped(true)。但在实际文件里光有换行还不够你还得让这一行有足够的高度不然内容会被遮住。EasyExcel 里可以在实体类上用ContentRowHeight注解也可以在我们的 handler 里通过writeRowHolder.getRow()动态设置行高writeRowHolder.getRow().setHeightInPoints(30f);如果你在备注里写了\n但 Excel 里还是显示一行先排查是不是setWrapped(true)没生效再看是不是\n被别的逻辑吞掉了。注意Java 字符串里的\n写到 Excel 就是换行符不需要换成别的字符。5.4 版本与依赖问题NoSuchFieldError factory、libfreetype6热词里提到的NoSuchFieldError: factory十有八九是 EasyExcel 和 POI 的版本不匹配导致的。EasyExcel 3.x 内部依赖了特定版本的 POI如果你为了用某个功能手动加了更高版本或更低版本的 POI 依赖就可能出现类冲突、字段找不到的情况。我的建议是别自己手工去调 POI 版本直接用 EasyExcel 传递依赖的那一套除非你有非常明确的兼容性要求。如果项目里其他地方必须用别的 POI 版本就用 Maven 的 dependencyManagement 把版本对齐到 EasyExcel 官方推荐的版本。libfreetype6这个问题实际上和 EasyExcel 本身没关系是 Linux 服务器上缺了图片字体相关的系统库一般出现在导出带图片的 Excel 时。报错信息里会提示缺少libfreetype.so.6在 Debian/Ubuntu 上apt-get install libfreetype6装一下就好。这种环境问题虽然不难解决但在面试或群里讨论时经常和 EasyExcel 混在一起容易误导新人。5.5 嵌套 List 数据如何渲染并应用样式如何渲染嵌套 list一般有两种情况。第一种是实体类里有个字段本身是 List比如订单下面有多条商品明细EasyExcel 默认不帮你展开你需要在导出前把数据结构打平一条订单展开成多条商品行再用分组样式比如把订单号列只显示第一行来实现。第二种是完全没有实体类数据本身就是ListListString这种情况直接用EasyExcel.write(outputStream).head(headList).sheet().doWrite(dataListList)就能写出去样式策略依然可以用因为afterCellDispose回调跟数据源类型无关只要列号判断对了照样能加样式。如果是复杂嵌套模型想做类似导出一张子表的效果我通常建议在数据处理层做转换而不是硬靠 EasyExcel 的 API 去渲染深层次对象因为那样代码可读性和维护成本都很难受。6. 性能优化与最佳实践6.1 样式对象一定要复用别在回调里 new 个没完前面已经说过POI 的 CellStyle 在单个 workbook 里是有数量上限的。在自定义样式策略里WriteCellStyle本身可以通过getOrCreateStyle()复用但如果你硬要在afterCellDispose里创建多个WriteCellStyle实例比如每种颜色都 new 一个虽然最终 EasyExcel 会做一些合并但对象创建开销依然存在。更稳妥的做法是把所有的样式对象提前初始化好放到一个 Map 或者数组里缓存回调里只负责取用不再重复创建。下面这个思路可以参考private final MapString, WriteCellStyle styleCache new HashMap(); private WriteCellStyle getCachedStyle(String key, SupplierWriteCellStyle initializer) { return styleCache.computeIfAbsent(key, k - initializer.get()); }状态列根据值变色这种场景总共就那么几种状态完全可以把成功样式失败样式处理中样式都提前建好用状态值当 key 直接查。这样导出一百万行数据样式对象也就那几个性能稳稳的。6.2 大数据量导出时的几个配套手段样式策略只是导出性能的一部分。数据量大时我一般还会注意这几件事分页查询不要一次性把全表数据 load 进内存能用ExcelWriter边查边写就不要用doWrite一把梭写完后记得关闭流最好用 try-with-resources如果文件要传到对象存储写完本地临时文件再上传避免响应超时。这些是老生常谈但很多时候性能问题不是样式策略造成的而是数据加载方式太粗暴。6.3 把样式策略封装成可配置的组件最后说一个工程化建议。如果你所在的项目里到处都在导出 Excel样式需求五花八门我建议把样式策略从一个 handler 写死升级成一个规则引擎。具体做法是定义一个样式规则的抽象比如接口ExcelColumnStyleRule里面包含列号匹配条件、样式应用方法然后让 handler 持有ListExcelColumnStyleRule遍历匹配到就应用。这样新增一种列样式只需要新增一个规则实现类不需要去改 handler 的 switch 分支既符合开闭原则也更好测试。我在实际项目中踩过最大的坑就是样式对象没有缓存导致几十万行数据导出时内存暴涨、文件损坏。后来把样式缓存和规则分发做了重构导出耗时从二十多秒降到八秒以内。所以方案不在多把一套自定义样式策略的思路用透EasyExcel 导出这块基本就能应对绝大多数业务场景了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价