1. 为什么选择EasyExcel处理Excel数据每次接手需要处理Excel数据的Java项目时我总会想起早期被POI支配的恐惧。内存溢出、性能低下、代码冗长这些问题在数据量超过万行时尤为明显。直到三年前接触了阿里巴巴开源的EasyExcel才真正找到了Java处理Excel的优雅解决方案。EasyExcel最吸引我的特点是内存占用极低。传统POI需要将整个Excel加载到内存而EasyExcel采用逐行解析的机制。我做过实测处理10万行数据时POI需要约500MB内存而EasyExcel稳定在50MB以内。这种差异在服务器环境尤为关键能有效避免OOM内存溢出问题。另一个优势是API设计极其简洁。比如导出数据到ExcelPOI需要20行代码实现的功能EasyExcel3行就能搞定。这对于需要快速开发的后端项目来说能节省大量时间。我团队最近做的报表系统用EasyExcel重构后代码量减少了60%。实际项目中常见的三种使用场景数据导入用户上传的Excel文件解析入库比如批量导入学生成绩数据导出生成统计报表供下载比如月度销售报表数据转换不同系统间的Excel格式转换比如从旧系统迁移数据2. 快速上手基础读写操作2.1 环境准备新建Maven项目时建议直接添加最新稳定版依赖当前为3.3.2dependencies dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency !-- 可选Lombok简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.24/version scopeprovided/scope /dependency /dependencies2.2 数据导出实战先定义一个带注解的实体类Data public class User { ExcelProperty(用户ID) private Long id; ExcelProperty(value 用户名, index 0) private String name; ExcelProperty(注册时间) DateTimeFormat(yyyy-MM-dd HH:mm:ss) private Date registerTime; }最简单的导出代码示例public class ExcelExport { public static void main(String[] args) { String fileName user_export.xlsx; ListUser data generateTestData(1000); EasyExcel.write(fileName, User.class) .sheet(用户列表) .doWrite(data); } private static ListUser generateTestData(int count) { ListUser list new ArrayList(); for (int i 1; i count; i) { User user new User(); user.setId((long)i); user.setName(用户_ i); user.setRegisterTime(new Date()); list.add(user); } return list; } }几个实用技巧通过index属性控制列顺序DateTimeFormat注解自动格式化日期文件扩展名用.xlsx可获得更好的压缩率2.3 数据导入最佳实践导入需要先创建监听器Slf4j public class UserDataListener extends AnalysisEventListenerUser { private static final int BATCH_SIZE 100; private ListUser cachedList new ArrayList(BATCH_SIZE); Override public void invoke(User user, AnalysisContext context) { cachedList.add(user); if (cachedList.size() BATCH_SIZE) { saveData(); cachedList.clear(); } } Override public void doAfterAllAnalysed(AnalysisContext context) { if (!cachedList.isEmpty()) { saveData(); } log.info(导入完成); } private void saveData() { // 这里实现批量入库逻辑 userRepository.saveAll(cachedList); } }实际调用代码public class ExcelImport { public static void main(String[] args) { String fileName user_import.xlsx; EasyExcel.read(fileName, User.class, new UserDataListener()) .sheet() .doRead(); } }避坑指南大文件导入一定要分批处理如每100条保存一次复杂数据建议先校验再入库使用ExcelIgnore注解忽略不需要的列3. 高级功能与性能优化3.1 处理百万级数据当需要导出超大数据量时可以采用分批次写入策略public void exportLargeData() { String fileName large_data.xlsx; ExcelWriter excelWriter EasyExcel.write(fileName, User.class).build(); try { WriteSheet writeSheet EasyExcel.writerSheet(大数据).build(); for (int i 0; i 10; i) { // 每次写入10万条 excelWriter.write(generateBatchData(i, 100000), writeSheet); } } finally { if (excelWriter ! null) { excelWriter.finish(); } } }关键优化点使用ExcelWriter手动控制写入过程每批数据量建议控制在5-10万条一定要在finally块中关闭资源3.2 自定义样式与格式通过WriteHandler接口可以自定义样式public class StyleHandler implements WriteHandler { Override public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { if (isHead) { // 表头样式 CellStyle style writeSheetHolder.getSheet().getWorkbook().createCellStyle(); style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); cell.setCellStyle(style); } } }使用时注册处理器EasyExcel.write(fileName, User.class) .registerWriteHandler(new StyleHandler()) .sheet() .doWrite(data);3.3 动态列导出对于列不固定的场景可以使用DynamicHead功能ListListString head Arrays.asList( Arrays.asList(基本信息, 姓名), Arrays.asList(基本信息, 年龄), Arrays.asList(联系方式, 手机号) ); ListListObject data Arrays.asList( Arrays.asList(张三, 25, 13800138000), Arrays.asList(李四, 30, 13900139000) ); EasyExcel.write(fileName) .head(head) .sheet() .doWrite(data);4. 生产环境实战经验4.1 并发处理方案在高并发场景下我推荐两种方案方案一本地临时文件public void concurrentExport(HttpServletResponse response) throws IOException { String tempFile temp_ UUID.randomUUID() .xlsx; try { EasyExcel.write(tempFile, User.class) .sheet() .doWrite(data); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment;filenameexport.xlsx); Files.copy(Paths.get(tempFile), response.getOutputStream()); } finally { Files.deleteIfExists(Paths.get(tempFile)); } }方案二内存直接输出public void directExport(HttpServletResponse response) throws IOException { response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment;filenameexport.xlsx); EasyExcel.write(response.getOutputStream(), User.class) .sheet() .doWrite(data); }4.2 常见问题排查中文乱码问题确保文件头设置正确response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8);检查系统默认编码是否为UTF-8性能突然下降检查是否误用了同步锁确认磁盘IO是否成为瓶颈监控GC情况避免频繁Full GC内存泄漏排查使用-XX:HeapDumpOnOutOfMemoryError参数获取内存快照重点检查未关闭的ExcelWriter实例确认监听器中没有持有大对象引用4.3 监控与报警建议在生产环境中建议添加以下监控指标导出任务平均耗时单任务最大内存占用并发导出任务数失败任务比例可以在监听器中添加统计逻辑public class MonitorListener extends AnalysisEventListenerUser { private Long startTime; private AtomicInteger counter new AtomicInteger(); Override public void invokeHeadMap(MapInteger, String headMap, AnalysisContext context) { startTime System.currentTimeMillis(); } Override public void doAfterAllAnalysed(AnalysisContext context) { long cost System.currentTimeMillis() - startTime; Metrics.timer(excel.import.time).record(cost, TimeUnit.MILLISECONDS); Metrics.counter(excel.import.rows).increment(counter.get()); } }