EasyExcel

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。


二、核心原理(低内存关键)

  1. XLSX(07+)
    • 不把 ZIP/XML 全加载内存
    • SAX 流式解析 XML一行行读 / 写
    • 共享字符串(sharedStrings)磁盘缓存 + 分批加载
  2. XLS(03)
    • 复用 POI 事件模型,同样不加载全文件EasyExcel
  3. 写文件
    • 流式生成,分批刷盘,内存只保留少量数据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);
}

八、常见问题与坑

  1. 监听器不能被 Spring 管理
    • 必须 new ,用构造器注入 Service
  2. 日期 / 数字格式错误
    • @DateTimeFormat / @NumberFormat
    • 或自定义 Converter
  3. OOM 依然出现
    • 检查是否一次性加载全量数据
    • 读:分批入库 ;写:分批查询 + 分批写
  4. 表头不匹配
    • 优先用 index 固定列
    • 或开启 autoTrim=true、忽略大小写
  5. 空行 / 异常行
    • invoke() 中判断并过滤
  6. Long / 雪花算法 ID 精度丢失
    • String 再导出

九、版本选择

  • 3.x / 4.x:推荐(低内存、稳定、新特性)
  • 2.x:老项目兼容
  • 不建议 1.x

十、总结

EasyExcel = 低内存 + 简洁 API + 高性能

  • :自定义监听器 + 分批处理
  • :注解映射 + 样式 / 合并 / 分批
  • 场景:报表导出、批量导入、大数据量 Excel
相关推荐
鉴生Eric1 小时前
拉孚BMA系统物联网架构:全面赋能传统楼宇BA系统的数字化转型
java·后端·struts
神奇小汤圆1 小时前
一文搞懂5种内存溢出案例,内含完整源码
后端
1368木林森2 小时前
深入浅出:JDK1.7→JDK1.8 HashMap进化史,再到ConcurrentHashMap的并发救赎
java·开发语言
小谢小哥2 小时前
50-Redis高级应用详解
后端
csbysj20202 小时前
Web 品质样式表:构建高效、美观的网页设计指南
开发语言
web3.08889992 小时前
tb关键词API接口——解锁独一无二的商品
java·数据库·https
小白学大数据2 小时前
企业精准数据分析双路径对比:运营商大数据与 Python 爬虫技术选型与实践
大数据·开发语言·爬虫·python·数据分析
敖正炀2 小时前
集合-Map深入分析
java
Hello!!!!!!2 小时前
C++基础(五)——屏幕和文件输入输出
开发语言·c++·算法