EasyExcel 初使用—— Java 实现读取 Excel 功能
'# EasyExcel 初使用—— Java 实现读取 Excel 功能
一、背景与问题
在企业级开发中,Excel 文件的读取和处理是常见需求。传统做法通常使用 Apache POI,但其存在以下痛点:
- 内存占用高(加载整个 Excel 文件到内存)
- 处理大数据量时易触发 OutOfMemoryError
- 代码复杂度高(需要处理 Workbook、Sheet、Row 等对象)
- 对 Excel 2007+ 的 xlsx 格式支持不完善
EasyExcel 作为阿里巴巴开源的 Excel 处理库,通过以下创新解决了上述问题:
- 基于 SAX 模式逐行读取,避免内存溢出
- 支持大文件处理(可处理超过 100M 的 Excel 文件)
- 提供丰富的数据转换和校验机制
- 支持多种格式(xls/xlsx/et)和多种数据类型(日期/数字/字符串)
在实际项目中,我们常需要处理用户导入导出功能,比如订单数据导入、报表统计等场景。EasyExcel 的设计正好契合这些需求。
二、基本原理
1. 核心架构
EasyExcel 的核心架构分为三个层次:
- 数据模型层:定义 Java Bean 与 Excel 单元格的映射关系
- 解析层:基于 SAX 模式逐行解析 Excel 文件
- 处理层:支持数据转换、校验、回调等业务逻辑
其核心优势在于通过 SAX 解析 实现流式处理,避免一次性加载整个文件到内存。这种设计使得 EasyExcel 能轻松处理数百万行数据。
2. 工作机制
EasyExcel 通过以下流程读取 Excel 文件:
- 创建 ExcelReader 对象,指定文件路径和数据模型
- 通过监听器(ReadListener)逐行处理数据
- 使用反射机制将单元格值转换为 Java 对象属性
- 支持自定义校验规则(如必填字段校验、格式校验)
其关键在于使用 SAX 解析器(Simple API for XML),这与传统 DOM 解析(一次性加载整个 XML)形成鲜明对比。
三、环境准备
1. 依赖引入
在 Maven 项目中添加以下依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>2. 开发环境
- JDK 1.8+
- IDE:IntelliJ IDEA / Eclipse
- 开发语言:Java
四、核心实现
1. 基础读取示例
public class EasyExcelDemo {
public static void main(String[] args) {
String fileName = "test.xlsx";
try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet, new PageReadListener<ExcelData>(dataList -> {
for (ExcelData data : dataList) {
System.out.println("读取到数据:" + data);
}
}));
} catch (Exception e) {
System.err.println("读取Excel时发生异常:" + e.getMessage());
}
}
}关键代码解释:
EasyExcel.read(fileName).build()创建 ExcelReader 实例readSheet(0)指定读取第一个工作表PageReadListener实现分页读取,避免内存溢出try-with-resources确保资源释放
2. 带头读取示例
public class HeaderReadDemo {
public static void main(String[] args) {
String fileName = "header.xlsx";
EasyExcel.read(fileName, ExcelData.class, new PageReadListener<ExcelData>(dataList -> {
for (ExcelData data : dataList) {
System.out.println("读取到数据:" + data);
}
})).sheet().doRead();
}
}关键代码解释:
ExcelData.class指定数据模型类sheet()方法指定读取的工作表(默认第一个)- 自动识别表头,将第一行作为字段映射
3. 自定义监听器示例
public class CustomReadListener implements ReadListener<ExcelData> {
@Override
public void invoke(ExcelData data, AnalysisContext context) {
System.out.println("处理数据:" + data);
}
@Override
public void onException(Exception exception, AnalysisContext context) {
System.err.println("读取异常:" + exception.getMessage());
}
@Override
public void doReadFinish(boolean readAllSuccess, AnalysisContext context) {
System.out.println("读取完成,共读取" + readAllSuccess ? "成功" : "失败");
}
}关键代码解释:
invoke方法处理每行数据onException处理异常doReadFinish读取完成后回调
五、完整案例
1. 业务场景
假设需要读取用户导入的订单数据,包含以下字段:
- 订单号(String)
- 产品名称(String)
- 数量(Integer)
- 价格(Double)
- 创建时间(LocalDateTime)
2. 数据模型类
public class OrderData {
@ExcelProperty(index = 0)
private String orderId;
@ExcelProperty(index = 1)
private String productName;
@ExcelProperty(index = 2)
private int quantity;
@ExcelProperty(index = 3)
private double price;
@ExcelProperty(index = 4)
private LocalDateTime createTime;
// Getter 和 Setter 方法
}3. 完整读取流程
public class OrderReadDemo {
public static void main(String[] args) {
String fileName = "orders.xlsx";
EasyExcel.read(fileName, OrderData.class, new PageReadListener<OrderData>(dataList -> {
for (OrderData data : dataList) {
System.out.println("订单号:" + data.getOrderId() +
", 产品:" + data.getProductName() +
", 数量:" + data.getQuantity());
}
})).sheet().doRead();
}
}关键点说明:
- 使用
@ExcelProperty注解定义字段映射关系 - 自动处理日期格式转换(需配合
@DateTimeFormat) - 支持自定义转换器(如自定义价格处理)
六、源码解析
1. 核心类分析
ExcelReader 是核心类,其内部使用 SAXParser 实现流式处理:
public class ExcelReader {
private final SAXParser saxParser;
public ExcelReader(String fileName) {
this.saxParser = SAXParserFactory.newInstance().newSAXParser();
}
public void read(ReadSheet readSheet, ReadListener readListener) {
// 实现 SAX 解析逻辑
}
}2. 解析流程
EasyExcel 使用 ContentHandler 接口实现解析:
public class ExcelContentHandler implements ContentHandler {
@Override
public void startElement(String name, Attributes attributes) {
// 处理开始标签
}
@Override
public void endElement(String name) {
// 处理结束标签
}
@Override
public void characters(char[] ch, int start, int length) {
// 处理文本内容
}
}七、进阶使用
1. 多线程处理
public class MultiThreadReadDemo {
public static void main(String[] args) {
String fileName = "large_data.xlsx";
List<ExcelReader> readers = new ArrayList<>();
for (int i = 0; i < 4; i++) {
readers.add(EasyExcel.read(fileName).build());
}
List<Thread> threads = new ArrayList<>();
for (ExcelReader reader : readers) {
Thread thread = new Thread(() -> {
ReadSheet readSheet = EasyExcel.readSheet(0).build();
reader.read(readSheet, new PageReadListener<ExcelData>(dataList -> {
// 处理数据
}));
});
threads.add(thread);
}
for (Thread thread : threads) {
thread.start();
}
}
}2. 自定义转换器
public class CustomConverter implements Converter<String> {
@Override
public String convertToJavaType(Class<?> javaType, Object value) {
return value.toString();
}
@Override
public Object convertToExcelType(Class<?> javaType, Object value) {
return value.toString();
}
}八、性能与工程实践
1. 性能优化方法
| 优化策略 | 说明 |
|---|---|
| 分页读取 | 默认每页 1000 条数据,可自定义页大小 |
| 多线程处理 | 多线程并行处理数据 |
| 内存管理 | 避免在监听器中缓存大量数据 |
| 异常处理 | 通过 onException 捕获异常并处理 |
2. 安全风险分析
- 文件格式风险:恶意构造的 Excel 文件可能引发内存泄漏
- 数据类型转换风险:不当的转换可能导致数据丢失
- XSS 攻击:直接输出用户输入内容可能造成注入攻击
3. 异常处理策略
public class SafeReadListener implements ReadListener<ExcelData> {
@Override
public void invoke(ExcelData data, AnalysisContext context) {
try {
// 处理数据逻辑
} catch (Exception e) {
System.err.println("处理数据时发生异常:" + e.getMessage());
}
}
@Override
public void onException(Exception exception, AnalysisContext context) {
System.err.println("读取异常:" + exception.getMessage());
}
}九、常见问题与踩坑
1. 常见错误
| 错误类型 | 原因 | 解决方案 |
|---|---|---|
| 内存溢出 | 大文件未分页读取 | 使用分页读取 |
| 数据类型错误 | 字段类型不匹配 | 使用 @ExcelProperty 明确类型 |
| 文件格式错误 | 使用了不支持的 Excel 格式 | 检查文件扩展名和内容 |
| 读取失败 | 文件路径错误 | 检查文件路径和权限 |
2. 典型问题
问题1:读取空文件时抛出异常
// 错误代码
EasyExcel.read("empty.xlsx").sheet().doRead();解决方案:
// 正确代码
EasyExcel.read("empty.xlsx").sheet().doRead();问题2:未正确关闭资源
// 错误代码
ExcelReader reader = EasyExcel.read(fileName).build();
reader.read(...);解决方案:
// 正确代码
try (ExcelReader reader = EasyExcel.read(fileName).build()) {
reader.read(...);
}十、最佳实践
1. 推荐方案
- 分页读取:始终使用分页机制处理大数据
- 数据校验:在
invoke方法中加入数据校验逻辑 - 异常处理:实现完整的异常处理链
- 资源管理:使用 try-with-resources 管理资源
- 性能监控:监控读取速度和内存使用情况
2. 避免错误做法
- 避免一次性加载全部数据:可能导致内存溢出
- 避免直接输出原始数据:防止 XSS 攻击
- 避免硬编码文件路径:使用配置管理
- 避免未处理的异常:可能导致程序崩溃
十一、总结
EasyExcel 作为 Java 中处理 Excel 文件的优秀工具,通过 SAX 解析机制实现了高效、安全的数据读取。其核心优势在于:
- 避免内存溢出的流式处理
- 支持大数据量处理
- 提供灵活的校验和转换机制
- 丰富的扩展能力
在实际开发中,建议:
- 对大数据量处理使用 EasyExcel
- 对小规模数据可考虑 POI
- 重视异常处理和资源管理
- 配合配置中心管理文件路径
通过合理使用 EasyExcel,可以显著提升 Excel 数据处理的效率和安全性,同时避免传统方法带来的诸多问题。
评论已关闭