EasyExcel 是阿里巴巴开源的 Java Excel 处理框架 ,核心优势是低内存占用、高性能 ,专门解决 Apache POI 处理大文件时的 OOM(内存溢出)问题,支持 .xls(2003)、.xlsx(2007+) 格式EasyExcelEasyExcel。
一、核心优势(vs POI)
表格
| 特性 | Apache POI | EasyExcel |
|---|---|---|
| 内存 | 全量加载到内存,百万行占数 GB | 逐行流式解析,默认缓存≈1MB |
| 速度 | 大数据量慢、易 Full GC | 快 3~5 倍 |
| API | 复杂、需懂 Excel 底层 | 注解 + 简洁 API,易上手EasyExcel |
| 大文件 | 极易 OOM | 百万行稳定处理 |
| 依赖 | 重、版本冲突多 | 轻量、依赖干净EasyExcel |
不支持:图片读取、Excel 宏、单个文件并发读写EasyExcel。
二、核心原理(低内存关键)
- XLSX(07+)
- 不把 ZIP/XML 全加载内存
- 用 SAX 流式解析 XML ,一行行读 / 写
- 共享字符串(sharedStrings)磁盘缓存 + 分批加载
- XLS(03)
- 复用 POI 事件模型,同样不加载全文件EasyExcel
- 写文件
- 流式生成,分批刷盘,内存只保留少量数据EasyExcel
三、Maven/Gradle 依赖(4.x 稳定版)
<!-- 核心 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
四、核心注解(实体类映射)
1. 常用注解
@ExcelProperty:列映射(value = 表头名 /index = 列序号)EasyExcel@ExcelIgnore:忽略该字段EasyExcel@ColumnWidth:列宽(像素)EasyExcel@ContentRowHeight:内容行高EasyExcel@HeadRowHeight:表头行高EasyExcel@DateTimeFormat:日期格式化(如"yyyy-MM-dd HH:mm")EasyExcel@NumberFormat:数字格式化(如"#.##%")EasyExcel
2. 实体类示例
import com.alibaba.excel.annotation.*;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.Data;
import java.util.Date;
@Data
public class UserData {
@ExcelProperty(value = "姓名", index = 0)
private String name;
@ExcelProperty(value = "年龄", index = 1)
private Integer age;
@ExcelProperty(value = "生日", index = 2)
@DateTimeFormat("yyyy-MM-dd")
private Date birthday;
@ExcelProperty(value = "薪资", index = 3)
@ColumnWidth(15)
private Double salary;
@ExcelIgnore // 不导出/导入
private String remark;
}
五、读 Excel(导入)
1. 核心组件
- AnalysisEventListener :行读取监听器(必须自定义)
- 分批入库:避免一次加载太多数据
2. 监听器(关键)
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class UserDataListener extends AnalysisEventListener<UserData> {
// 每批处理条数(建议1000~3000)
private static final int BATCH_COUNT = 1000;
private final List<UserData> cacheList = new ArrayList<>(BATCH_COUNT);
// 注入Service/DAO(Spring下用构造器传入)
private final UserService userService;
public UserDataListener(UserService userService) {
this.userService = userService;
}
// 每读一行触发
@Override
public void invoke(UserData data, AnalysisContext context) {
cacheList.add(data);
// 满批则入库
if (cacheList.size() >= BATCH_COUNT) {
saveData();
cacheList.clear();
}
}
// 全部读完
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData(); // 剩余数据入库
log.info("Excel读取完成");
}
// 异常处理(格式错误等)
@Override
public void onException(Exception exception, AnalysisContext context) {
log.error("解析失败,行号:{}", context.readRowHolder().getRowIndex(), exception);
if (exception instanceof ExcelDataConvertException) {
// 可自定义处理格式错误
}
}
// 批量入库
private void saveData() {
if (cacheList.isEmpty()) return;
userService.batchInsert(cacheList);
}
}
3. 读取代码
// 同步读(文件/InputStream)
EasyExcel.read("user.xlsx", UserData.class, new UserDataListener(userService))
.sheet() // 读取第一个Sheet
.headRowNumber(1) // 表头行数(默认1)
.doRead();
// 读指定Sheet(第2个Sheet)
.sheet(1)
// 读指定Sheet名
.sheet("用户表")
// 跳过前N行
.registerReadListener(new AbstractIgnoreExceptionReadListener() {
@Override
public boolean hasNext(AnalysisContext context) {
return context.readRowHolder().getRowIndex() > 2; // 跳过前3行
}
})
六、写 Excel(导出)
1. 简单写入(Web 导出 / 本地文件)
// 1. 数据
List<UserData> list = userService.listAll();
// 2. 写入本地文件
EasyExcel.write("user_export.xlsx", UserData.class)
.sheet("用户信息")
.doWrite(list);
// 3. Web 导出(核心)
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("用户列表_" + System.currentTimeMillis(), "UTF-8");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), UserData.class)
.sheet("用户数据")
.doWrite(list);
2. 复杂写入(常用)
-
自定义样式(表头 / 内容)
-
合并单元格
-
动态表头
-
图片 / 超链接(有限支持)
// 样式策略(示例:表头灰色、内容居中)
HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(
EasyExcelUtil.getHeadStyle(), // 表头样式
EasyExcelUtil.getContentStyle() // 内容样式
);EasyExcel.write(out, UserData.class)
.registerWriteHandler(styleStrategy) // 注册样式
.sheet("用户表")
.doWrite(list);
3. 大数据量分批写(避免 OOM)
try (ExcelWriter writer = EasyExcel.write(out, UserData.class).build()) {
WriteSheet sheet = EasyExcel.writerSheet(0, "用户数据").build();
// 分页查询
for (int page = 1; ; page++) {
List<UserData> pageList = userService.pageList(page, 1000);
if (pageList.isEmpty()) break;
writer.write(pageList, sheet);
}
}
七、Web 最佳实践
1. 导入(上传)
@PostMapping("/import")
public String importExcel(@RequestParam("file") MultipartFile file) throws Exception {
EasyExcel.read(file.getInputStream(), UserData.class, new UserDataListener(userService))
.sheet()
.doRead();
return "导入成功";
}
2. 导出(下载)
@GetMapping("/export")
public void export(HttpServletResponse response) throws Exception {
List<UserData> list = userService.listAll();
// 设响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("用户列表", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), UserData.class)
.sheet("用户数据")
.doWrite(list);
}
八、常见问题与坑
- 监听器不能被 Spring 管理
- 必须 new ,用构造器注入 Service
- 日期 / 数字格式错误
- 用
@DateTimeFormat/@NumberFormat - 或自定义 Converter
- 用
- OOM 依然出现
- 检查是否一次性加载全量数据
- 读:分批入库 ;写:分批查询 + 分批写
- 表头不匹配
- 优先用
index固定列 - 或开启
autoTrim=true、忽略大小写
- 优先用
- 空行 / 异常行
- 在
invoke()中判断并过滤
- 在
- Long / 雪花算法 ID 精度丢失
- 转 String 再导出
九、版本选择
- 3.x / 4.x:推荐(低内存、稳定、新特性)
- 2.x:老项目兼容
- 不建议 1.x
十、总结
EasyExcel = 低内存 + 简洁 API + 高性能
- 读:自定义监听器 + 分批处理
- 写:注解映射 + 样式 / 合并 / 分批
- 场景:报表导出、批量导入、大数据量 Excel