ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

EasyExcel高效处理Excel数据的技术解析与实践

EasyExcel高效处理Excel数据的技术解析与实践 1. 为什么选择EasyExcel处理Excel数据在日常开发中Excel文件处理是个高频需求场景。传统使用Apache POI的方案存在明显痛点内存占用高一个10MB的Excel可能消耗500MB内存、API设计复杂需要处理各种CellStyle和Workbook对象、对大文件支持差容易OOM。而阿里开源的EasyExcel通过创新的设计解决了这些问题。我去年接手的一个供应链管理系统项目需要每天处理300个平均50MB的供应商Excel报价单。最初用POI导致频繁Full GC改用EasyExcel后内存稳定控制在100MB以内解析速度提升4倍。这个实战经历让我深刻体会到工具选型的重要性。2. 环境准备与快速开始2.1 基础环境配置推荐使用Maven项目添加最新依赖截至2023年8月为3.3.2dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency注意版本兼容性JDK8环境可用全功能Android平台需使用2.x版本若需读写xls格式HSSF需额外引入poi依赖2.2 第一个读写示例创建实体类使用Lombok简化代码Data public class Product { ExcelProperty(产品ID) private String id; ExcelProperty(value 产品名称, index 1) private String name; ExcelProperty(converter BigDecimalNumberConverter.class) private BigDecimal price; }写入Excel的极简代码// 写法1使用路径 String fileName product.xlsx; EasyExcel.write(fileName, Product.class) .sheet(产品列表) .doWrite(dataList); // 写法2输出到HttpServletResponseWeb场景 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); EasyExcel.write(response.getOutputStream(), Product.class) .sheet(产品列表) .doWrite(dataList);3. 核心功能深度解析3.1 智能内存管理机制EasyExcel采用滑动窗口分段加载的设计默认每1000行触发一次回调可通过pageSize参数调整采用文件流式写入不缓存完整数据使用SAX模式解析避免DOM内存膨胀内存对比测试1GB的xlsx文件方案最大堆内存解析时间POI-XSSF2.5GB78sEasyExcel300MB32s3.2 复杂表头处理技巧多层表头配置示例HeadStyle(fillPatternType FillPatternType.SOLID_FOREGROUND, fillForegroundColor 10) HeadFontStyle(fontHeightInPoints 12) public class MultiHeaderVO { ExcelProperty({主分类, 子分类, 字段名}) private String field1; ExcelProperty(value {主分类, 子分类, 字段名}, index 1) private String field2; }样式控制技巧使用ContentStyle注解设置数据行样式通过ColumnWidth设置列宽单位字符动态样式可通过实现CellWriteHandler接口实现3.3 自定义类型转换实现Converter接口处理特殊类型public class StatusConverter implements ConverterInteger { Override public Integer convertToJavaData(ReadContext context) { return 启用.equals(context.readCellData().getStringValue()) ? 1 : 0; } Override public CellData convertToExcelData(Integer value) { return new CellData(value 1 ? 启用 : 停用); } }4. 高级应用场景4.1 百万级数据导出优化关键配置参数ExcelWriterBuilder builder EasyExcel.write(response.getOutputStream()) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动列宽 .autoCloseStream(true) .inMemory(false) // 启用磁盘缓存 .file(new File(/temp/cache.tmp)); // 缓存文件路径 // 分批次写入 for (int i 0; i totalPage; i) { ListProduct batchData queryBatchData(i); builder.sheet(第(i1)页).doWrite(batchData); }4.2 动态列导出方案通过WriteHandler实现动态列public class DynamicColumnHandler implements SheetWriteHandler { Override public void afterSheetCreate(WriteContext context) { Sheet sheet context.writeSheetHolder().getSheet(); // 动态添加表头 Row headerRow sheet.createRow(0); for (int i 0; i dynamicFields.size(); i) { headerRow.createCell(i).setCellValue(dynamicFields.get(i)); } } }4.3 复杂模板填充模板示例template.xlsx| 订单号 | ${orderNo} | | 客户名 | ${customerName} | | 商品列表 | | 商品ID | 商品名称 | 单价 | | ${goods[0].id} | ${goods[0].name} | ${goods[0].price} |填充代码MapString, Object params new HashMap(); params.put(orderNo, NO202308001); params.put(customerName, 测试客户); ListGoods goodsList queryGoods(); params.put(goods, goodsList); EasyExcel.write(filled.xlsx) .withTemplate(template.xlsx) .sheet() .doFill(params);5. 实战问题排查指南5.1 常见异常处理Excel文件损坏现象打开文件提示文件已损坏检查确保流正确关闭autoCloseStream(true)解决方案添加响应头response.setHeader(Content-Disposition, attachment)日期格式错乱现象Excel中显示数字而非日期正确用法实体类中使用DateTimeFormat(yyyy-MM-dd)注解内存溢出现象OutOfMemoryError: Java heap space配置确保设置inMemory(false)并指定临时文件路径5.2 性能调优参数参数默认值建议值大数据量作用说明readCacheSize10005000读取缓存行数writeCacheSize10002000写入缓存行数autoTrimtruefalse是否自动trim字符串useDefaultListenertruefalse是否使用默认监听器5.3 调试技巧启用调试日志EasyExcel.read(inputStream, DemoData.class, new DemoDataListener()) .ignoreEmptyRow(false) // 显示空行 .extraRead(CellExtraTypeEnum.COMMENT) // 读取注释 .sheet() .doRead();使用AnalysisEventListener的onException方法捕获解析异常Override public void onException(Exception exception, AnalysisContext context) { log.error(解析异常行号{}, context.readRowHolder().getRowIndex()); }6. 扩展应用方案6.1 与SpringBoot深度集成配置类示例Configuration public class ExcelConfig { Bean public EasyExcelFactoryBean easyExcel() { EasyExcelFactoryBean factory new EasyExcelFactoryBean(); factory.setDefaultConverter(new CustomConverter()); return factory; } Bean public ServletResponseCharacterEncoding responseEncoding() { return new ServletResponseCharacterEncoding(); } }Controller示例PostMapping(/import) public void importExcel(RequestParam MultipartFile file) { EasyExcel.read(file.getInputStream(), Product.class, new ProductDataListener(productService)) .sheet() .doRead(); } GetMapping(/export) public void exportExcel(HttpServletResponse response) { response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); EasyExcel.write(response.getOutputStream(), Product.class) .sheet(产品列表) .doWrite(productService.listAll()); }6.2 与数据库协同工作批量插入优化方案public class ProductDataListener extends AnalysisEventListenerProduct { private static final int BATCH_SIZE 1000; private ListProduct cachedList new ArrayList(BATCH_SIZE); Override public void invoke(Product data, AnalysisContext context) { cachedList.add(data); if (cachedList.size() BATCH_SIZE) { saveBatch(); cachedList.clear(); } } private void saveBatch() { jdbcTemplate.batchUpdate( INSERT INTO product(id,name,price) VALUES(?,?,?), new BatchPreparedStatementSetter() { // 实现批处理逻辑 }); } }6.3 生成可视化报表结合模板生成图表报表准备包含图表的模板文件使用ExcelIgnore注解排除非数据字段通过模板填充写入数据ExcelWriter excelWriter EasyExcel.write(outputStream) .withTemplate(templateFile) .build(); WriteSheet writeSheet EasyExcel.writerSheet().build(); excelWriter.fill(data, writeSheet); // 刷新图表需模板中预定义 Chart chart writeSheet.getSheet().getDrawingPatriarch().getCharts().get(0); ChartData chartData chart.getChartData(); // 更新数据范围...
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进