1. EasyExcel基础入门5分钟搞定Excel读取第一次接触EasyExcel时我被它的简洁API惊艳到了。记得之前用POI处理Excel时光是读取一个简单表格就要写几十行代码而EasyExcel只需要三行就能搞定。这个由阿里巴巴开源的工具专为解决Java中Excel处理痛点而生。先来看最基础的Maven依赖配置dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency假设我们要读取一个员工信息表包含姓名、工号和部门三个字段。首先创建对应的实体类Data public class Employee { ExcelProperty(姓名) private String name; ExcelProperty(工号) private String employeeId; ExcelProperty(部门) private String department; }最简读取代码长这样String fileName 员工表.xlsx; EasyExcel.read(fileName, Employee.class, new PageReadListenerEmployee(dataList - { dataList.forEach(emp - System.out.println(emp.getName()-emp.getDepartment())); })).sheet().doRead();这里用到的PageReadListener是EasyExcel内置的监听器默认每100条数据回调一次。我特别喜欢这种流式处理的设计相比一次性加载全部数据到内存这种方式对大数据量文件特别友好。2. 进阶读取技巧精准控制数据流2.1 指定列读取的两种姿势实际项目中经常遇到只需要读取部分列的情况。EasyExcel提供了两种方式第一种是通过ExcelProperty的index属性指定列下标从0开始Data public class EmployeePartial { ExcelProperty(index 1) // 只读取第二列 private String employeeId; }第二种是通过列名匹配更推荐这种方式Data public class EmployeePartial { ExcelProperty(工号) private String employeeId; }我在金融项目里处理对账单时表格可能有50列但只需要其中的交易时间和金额两列。用列名匹配的方式既直观又不怕列顺序调整代码可维护性大大提高。2.2 多Sheet处理的正确姿势处理多Sheet文件时最容易踩的坑是重复读取。来看正确做法// 方法1一次性读取所有Sheet EasyExcel.read(fileName, Employee.class, listener).doReadAll(); // 方法2选择性读取特定Sheet try (ExcelReader excelReader EasyExcel.read(fileName).build()) { ReadSheet sheet1 EasyExcel.readSheet(0).head(Employee.class) .registerReadListener(listener).build(); ReadSheet sheet2 EasyExcel.readSheet(季度报表) .head(Employee.class).registerReadListener(listener).build(); excelReader.read(sheet1, sheet2); }最近处理一个包含12个月数据的年度报表时我用了第二种方式。关键点是要把多个ReadSheet对象一次性传入read方法否则03版Excel会重复IO操作。3. 高级应用自定义转换与异常处理3.1 玩转数据转换器处理银行流水时金额需要自动加上¥前缀。通过自定义转换器轻松实现public class AmountConverter implements ConverterString { Override public String convertToJavaData(ReadConverterContext? context) { return ¥ context.getReadCellData().getStringValue(); } } Data public class BankStatement { ExcelProperty(converter AmountConverter.class) private String amount; }日期格式化也是常见需求Data public class EventSchedule { DateTimeFormat(yyyy/MM/dd HH:mm) private Date eventTime; }3.2 健壮性提升实战生产环境中最怕遇到格式错误的Excel文件。通过监听器的异常处理方法可以优雅处理public class SafeReadListener implements ReadListenerEmployee { Override public void onException(Exception e, AnalysisContext context) { if(e instanceof ExcelDataConvertException){ ExcelDataConvertException ex (ExcelDataConvertException)e; log.error(第{}行{}列数据格式错误, ex.getRowIndex(), ex.getColumnIndex()); } // 记录错误后继续处理后续行 } }在物流系统中我用这种方式处理过客户上传的破损Excel文件。即使部分数据有问题也能保证其他正常数据被正确处理而不是整个导入失败。4. 性能优化与内存管理4.1 批处理大小调优默认的100条批处理大小不一定适合所有场景。通过测试找到最优值// 测试不同batchSize的性能 for(int batchSize : new int[]{50,100,500,1000}){ long start System.currentTimeMillis(); EasyExcel.read(fileName, Employee.class, new PageReadListener(list - {}, batchSize)).sheet().doRead(); System.out.printf(batchSize%d 耗时%dms%n, batchSize, System.currentTimeMillis()-start); }在用户行为分析项目中我发现对于10万行以上的数据500-1000的batchSize性能最好。但要注意调大batchSize会增加单次内存消耗。4.2 大文件处理方案遇到GB级别的Excel文件时可以采用分片读取策略try (ExcelReader excelReader EasyExcel.read(fileName).build()) { ReadSheet readSheet EasyExcel.readSheet(0) .head(Employee.class) .registerReadListener(new AnalysisEventListenerEmployee() { Override public void invoke(Employee data, AnalysisContext context) { if(needBreak(data)){ // 自定义停止条件 context.readSheetHolder().stop(); } } }).build(); excelReader.read(readSheet); }曾经处理过一个2.3GB的物联网设备日志用这种方案成功在有限内存环境下完成了数据筛选。关键点是及时调用stop()方法中断读取。5. 实战案例销售报表分析系统最近给零售客户做的销售分析系统中我用EasyExcel处理了这些复杂场景多维度数据合并// 合并多个门店的日报表 MapString, SalesData resultMap new ConcurrentHashMap(); EasyExcel.read(fileName, SalesData.class, new AnalysisEventListenerSalesData() { Override public void invoke(SalesData data, AnalysisContext context) { resultMap.merge(data.getProductId(), data, this::mergeData); } }).doReadAll();动态列处理使用Map接收EasyExcel.read(fileName, new AnalysisEventListenerMapInteger, String() { Override public void invoke(MapInteger, String data, AnalysisContext context) { // 根据表头动态处理列 } }).sheet().doRead();与Spring Batch集成Bean public ItemReaderSalesData excelReader() { return () - { ListSalesData data new ArrayList(); EasyExcel.read(fileName, SalesData.class, new AnalysisEventListenerSalesData() { Override public void invoke(SalesData data, AnalysisContext context) { data.add(data); } }).sheet().doRead(); return data.iterator(); }; }这套系统每天处理200门店的销售数据峰值时每小时要处理5万条记录。EasyExcel的稳定表现让整个数据处理流程变得轻松可靠。
EasyExcel实战指南:从基础读取到高级应用
1. EasyExcel基础入门5分钟搞定Excel读取第一次接触EasyExcel时我被它的简洁API惊艳到了。记得之前用POI处理Excel时光是读取一个简单表格就要写几十行代码而EasyExcel只需要三行就能搞定。这个由阿里巴巴开源的工具专为解决Java中Excel处理痛点而生。先来看最基础的Maven依赖配置dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency假设我们要读取一个员工信息表包含姓名、工号和部门三个字段。首先创建对应的实体类Data public class Employee { ExcelProperty(姓名) private String name; ExcelProperty(工号) private String employeeId; ExcelProperty(部门) private String department; }最简读取代码长这样String fileName 员工表.xlsx; EasyExcel.read(fileName, Employee.class, new PageReadListenerEmployee(dataList - { dataList.forEach(emp - System.out.println(emp.getName()-emp.getDepartment())); })).sheet().doRead();这里用到的PageReadListener是EasyExcel内置的监听器默认每100条数据回调一次。我特别喜欢这种流式处理的设计相比一次性加载全部数据到内存这种方式对大数据量文件特别友好。2. 进阶读取技巧精准控制数据流2.1 指定列读取的两种姿势实际项目中经常遇到只需要读取部分列的情况。EasyExcel提供了两种方式第一种是通过ExcelProperty的index属性指定列下标从0开始Data public class EmployeePartial { ExcelProperty(index 1) // 只读取第二列 private String employeeId; }第二种是通过列名匹配更推荐这种方式Data public class EmployeePartial { ExcelProperty(工号) private String employeeId; }我在金融项目里处理对账单时表格可能有50列但只需要其中的交易时间和金额两列。用列名匹配的方式既直观又不怕列顺序调整代码可维护性大大提高。2.2 多Sheet处理的正确姿势处理多Sheet文件时最容易踩的坑是重复读取。来看正确做法// 方法1一次性读取所有Sheet EasyExcel.read(fileName, Employee.class, listener).doReadAll(); // 方法2选择性读取特定Sheet try (ExcelReader excelReader EasyExcel.read(fileName).build()) { ReadSheet sheet1 EasyExcel.readSheet(0).head(Employee.class) .registerReadListener(listener).build(); ReadSheet sheet2 EasyExcel.readSheet(季度报表) .head(Employee.class).registerReadListener(listener).build(); excelReader.read(sheet1, sheet2); }最近处理一个包含12个月数据的年度报表时我用了第二种方式。关键点是要把多个ReadSheet对象一次性传入read方法否则03版Excel会重复IO操作。3. 高级应用自定义转换与异常处理3.1 玩转数据转换器处理银行流水时金额需要自动加上¥前缀。通过自定义转换器轻松实现public class AmountConverter implements ConverterString { Override public String convertToJavaData(ReadConverterContext? context) { return ¥ context.getReadCellData().getStringValue(); } } Data public class BankStatement { ExcelProperty(converter AmountConverter.class) private String amount; }日期格式化也是常见需求Data public class EventSchedule { DateTimeFormat(yyyy/MM/dd HH:mm) private Date eventTime; }3.2 健壮性提升实战生产环境中最怕遇到格式错误的Excel文件。通过监听器的异常处理方法可以优雅处理public class SafeReadListener implements ReadListenerEmployee { Override public void onException(Exception e, AnalysisContext context) { if(e instanceof ExcelDataConvertException){ ExcelDataConvertException ex (ExcelDataConvertException)e; log.error(第{}行{}列数据格式错误, ex.getRowIndex(), ex.getColumnIndex()); } // 记录错误后继续处理后续行 } }在物流系统中我用这种方式处理过客户上传的破损Excel文件。即使部分数据有问题也能保证其他正常数据被正确处理而不是整个导入失败。4. 性能优化与内存管理4.1 批处理大小调优默认的100条批处理大小不一定适合所有场景。通过测试找到最优值// 测试不同batchSize的性能 for(int batchSize : new int[]{50,100,500,1000}){ long start System.currentTimeMillis(); EasyExcel.read(fileName, Employee.class, new PageReadListener(list - {}, batchSize)).sheet().doRead(); System.out.printf(batchSize%d 耗时%dms%n, batchSize, System.currentTimeMillis()-start); }在用户行为分析项目中我发现对于10万行以上的数据500-1000的batchSize性能最好。但要注意调大batchSize会增加单次内存消耗。4.2 大文件处理方案遇到GB级别的Excel文件时可以采用分片读取策略try (ExcelReader excelReader EasyExcel.read(fileName).build()) { ReadSheet readSheet EasyExcel.readSheet(0) .head(Employee.class) .registerReadListener(new AnalysisEventListenerEmployee() { Override public void invoke(Employee data, AnalysisContext context) { if(needBreak(data)){ // 自定义停止条件 context.readSheetHolder().stop(); } } }).build(); excelReader.read(readSheet); }曾经处理过一个2.3GB的物联网设备日志用这种方案成功在有限内存环境下完成了数据筛选。关键点是及时调用stop()方法中断读取。5. 实战案例销售报表分析系统最近给零售客户做的销售分析系统中我用EasyExcel处理了这些复杂场景多维度数据合并// 合并多个门店的日报表 MapString, SalesData resultMap new ConcurrentHashMap(); EasyExcel.read(fileName, SalesData.class, new AnalysisEventListenerSalesData() { Override public void invoke(SalesData data, AnalysisContext context) { resultMap.merge(data.getProductId(), data, this::mergeData); } }).doReadAll();动态列处理使用Map接收EasyExcel.read(fileName, new AnalysisEventListenerMapInteger, String() { Override public void invoke(MapInteger, String data, AnalysisContext context) { // 根据表头动态处理列 } }).sheet().doRead();与Spring Batch集成Bean public ItemReaderSalesData excelReader() { return () - { ListSalesData data new ArrayList(); EasyExcel.read(fileName, SalesData.class, new AnalysisEventListenerSalesData() { Override public void invoke(SalesData data, AnalysisContext context) { data.add(data); } }).sheet().doRead(); return data.iterator(); }; }这套系统每天处理200门店的销售数据峰值时每小时要处理5万条记录。EasyExcel的稳定表现让整个数据处理流程变得轻松可靠。