easyexcel-read

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

EasyExcel 数据读取(Read)

EasyExcel 数据读取(Read)

本技能专注于 "读取 Excel 数据" 场景。当用户需要解析
.xlsx
/
.xls
文件(导入数据、批量入库、对账文件解析、用户上传)时,应当使用本技能。
本技能专注于 "读取 Excel 数据" 场景。当用户需要解析
.xlsx
/
.xls
文件(导入数据、批量入库、对账文件解析、用户上传)时,应当使用本技能。

Capability Boundaries

能力边界

✅ 强项

✅ 强项

  1. 监听器模式(流式,大文件不 OOM)
  2. 同步读取(小文件一次性返回 List)
  3. 多 Sheet 读取(全部 / 部分)
  4. 多行表头支持(
    headRowNumber
    )
  5. 自定义转换器(日期/数字/枚举)
  6. 异常处理(行级容错)
  7. 额外信息读取(批注/超链接/合并单元格)
  8. 公式和单元格原始类型
  9. 不创建对象的读(直接得 Map)
  10. Web 上传文件读取
  1. 监听器模式(流式,大文件不 OOM)
  2. 同步读取(小文件一次性返回 List)
  3. 多 Sheet 读取(全部 / 部分)
  4. 多行表头支持(
    headRowNumber
    )
  5. 自定义转换器(日期/数字/枚举)
  6. 异常处理(行级容错)
  7. 额外信息读取(批注/超链接/合并单元格)
  8. 公式和单元格原始类型
  9. 不创建对象的读(直接得 Map)
  10. Web 上传文件读取

⚠️ 限制

⚠️ 限制

  1. 监听器不能被 Spring 管理(必须 new,且字段需要用构造方法传入 Spring Bean)
  2. 一个 sheet 不能重复读取(多次读取需重新构造 reader)
  3. 03 版多 sheet 一次性传入避免重复解析
  4. headRowNumber
    不指定时会按
    @ExcelProperty#value()
    表头数量推断
  5. doReadSync()
    不推荐大数据量
  1. 监听器不能被 Spring 管理(必须 new,且字段需要用构造方法传入 Spring Bean)
  2. 一个 sheet 不能重复读取(多次读取需重新构造 reader)
  3. 03 版多 sheet 一次性传入避免重复解析
  4. headRowNumber
    不指定时会按
    @ExcelProperty#value()
    表头数量推断
  5. doReadSync()
    不推荐大数据量

❌ Out of Scope(不该用本技能的场景,请改用其它技能)

❌ 范围外(不该用本技能的场景,请改用其它技能)

  1. 按预制模板填充 → 不适用本技能,使用
    easyexcel-fill
  2. 程序化生成 Excel → 不适用本技能,使用
    easyexcel-write
  3. CSV 文件 → 不适用本技能,自行使用 commons-csv / OpenCSV(EasyExcel 也支持 CSV,需
    excelType(CSV)
    )
  4. Word/PPT → 不适用本技能,使用 Apache POI
  1. 按预制模板填充 → 不适用本技能,使用
    easyexcel-fill
  2. 程序化生成 Excel → 不适用本技能,使用
    easyexcel-write
  3. CSV 文件 → 不适用本技能,自行使用 commons-csv / OpenCSV(EasyExcel 也支持 CSV,需
    excelType(CSV)
    )
  4. Word/PPT → 不适用本技能,使用 Apache POI

Data Privacy

数据隐私

本技能不收集、存储或传输任何用户数据。所有代码示例仅用于本地开发参考。
本技能不收集、存储或传输任何用户数据。所有代码示例仅用于本地开发参考。

When to use this skill

何时使用此技能

  • 用户说"读取 Excel"、"解析 Excel"、"导入 Excel"、"上传 Excel"
  • 用户上传
    .xlsx
    文件,要求把数据读为 Java 对象或 Map
  • 业务上有"批量入库"、"对账文件解析"、"动态列读取"等需求
  • 需要读取批注/超链接/合并单元格等额外信息
  • 用户说"读取 Excel"、"解析 Excel"、"导入 Excel"、"上传 Excel"
  • 用户上传
    .xlsx
    文件,要求把数据读为 Java 对象或 Map
  • 业务上有"批量入库"、"对账文件解析"、"动态列读取"等需求
  • 需要读取批注/超链接/合并单元格等额外信息

Quick Start

快速开始

典型调用方式(4 种写法):
java
// 写法 1:JDK8+ PageReadListener(since 3.0.0-beta1,最简洁)
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
    for (DemoData data : dataList) {
        log.info("读取到数据: {}", JSON.toJSONString(data));
    }
})).sheet().doRead();

// 写法 2:匿名 ReadListener
EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
    @Override public void invoke(DemoData data, AnalysisContext context) { /* 每行回调 */ }
    @Override public void doAfterAllAnalysed(AnalysisContext context) { /* 全部完成 */ }
}).sheet().doRead();

// 写法 3:自定义 ReadListener
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

// 写法 4:ExcelReader(一个文件一个 reader)
try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class,
        new DemoDataListener()).build()) {
    ReadSheet readSheet = EasyExcel.readSheet(0).build();
    excelReader.read(readSheet);
}
官方原文:"PageReadListener 默认每次会读取100条数据 然后返回过来 直接调用使用数据就行"
典型调用方式(4 种写法):
java
// 写法 1:JDK8+ PageReadListener(since 3.0.0-beta1,最简洁)
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
    for (DemoData data : dataList) {
        log.info("读取到数据: {}", JSON.toJSONString(data));
    }
})).sheet().doRead();

// 写法 2:匿名 ReadListener
EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
    @Override public void invoke(DemoData data, AnalysisContext context) { /* 每行回调 */ }
    @Override public void doAfterAllAnalysed(AnalysisContext context) { /* 全部完成 */ }
}).sheet().doRead();

// 写法 3:自定义 ReadListener
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

// 写法 4:ExcelReader(一个文件一个 reader)
try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class,
        new DemoDataListener()).build()) {
    ReadSheet readSheet = EasyExcel.readSheet(0).build();
    excelReader.read(readSheet);
}
官方原文:"PageReadListener 默认每次会读取100条数据 然后返回过来 直接调用使用数据就行"

Workflow

工作流程

Step 1. 确认场景 — 数据规模、是否包含额外信息、是否有 DTO Step 2. 定义数据模型 — 编写 DTO/VO,标注
@ExcelProperty
等注解 Step 3. 选择读取方式 — 监听器(流式)/ 同步(小文件)/ 不创建对象 Step 4. 实现 ReadListener — 注入 DAO(构造方法);处理
invoke
/
doAfterAllAnalysed
/
onException
/
invokeHead
/
extra
Step 5. 执行读取 —
doRead()
或
excelReader.read(readSheet)
Step 6. 验证输出 — 检查行数、转换异常、批注/超链接捕获
步骤 1. 确认场景 — 数据规模、是否包含额外信息、是否有 DTO 步骤 2. 定义数据模型 — 编写 DTO/VO,标注
@ExcelProperty
等注解 步骤 3. 选择读取方式 — 监听器(流式)/ 同步(小文件)/ 不创建对象 步骤 4. 实现 ReadListener — 注入 DAO(构造方法);处理
invoke
/
doAfterAllAnalysed
/
onException
/
invokeHead
/
extra
步骤 5. 执行读取 —
doRead()
或
excelReader.read(readSheet)
步骤 6. 验证输出 — 检查行数、转换异常、批注/超链接捕获

Critical: 模式选择决策

关键:模式选择决策

数据规模?
├── < 1000 行 + 一次性返回 → 同步模式:doReadSync()
├── < 1 万行 + 简单处理 → PageReadListener
├── > 1 万行 + 入库 → ReadListener + 批量入库(每 2000 行)
└── 不需要对象 + 动态列 → ReadListener<Map<Integer, String>>

需要额外信息?
├── 批注 → extraRead(CellExtraTypeEnum.COMMENT)
├── 超链接 → extraRead(CellExtraTypeEnum.HYPERLINK)
└── 合并单元格 → extraRead(CellExtraTypeEnum.MERGE)

需要多 Sheet?
├── 全部 → doReadAll()
└── 部分 → ExcelReader.read(readSheet1, readSheet2, ...)
数据规模?
├── < 1000 行 + 一次性返回 → 同步模式:doReadSync()
├── < 1 万行 + 简单处理 → PageReadListener
├── > 1 万行 + 入库 → ReadListener + 批量入库(每 2000 行)
└── 不需要对象 + 动态列 → ReadListener<Map<Integer, String>>

需要额外信息?
├── 批注 → extraRead(CellExtraTypeEnum.COMMENT)
├── 超链接 → extraRead(CellExtraTypeEnum.HYPERLINK)
└── 合并单元格 → extraRead(CellExtraTypeEnum.MERGE)

需要多 Sheet?
├── 全部 → doReadAll()
└── 部分 → ExcelReader.read(readSheet1, readSheet2, ...)

Critical: 标准监听器模板

关键:标准监听器模板

java
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
    /**
     * 官方原文:"DemoDataListener 不能被spring管理,要每次读取excel都要new"
     */
    private static final int BATCH_COUNT = 100;
    private List<DemoData> cachedDataList =
        ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    private DemoDAO demoDAO;

    public DemoDataListener() {
        demoDAO = new DemoDAO();
    }

    /**
     * 官方原文:"如果使用了spring,请使用这个构造方法。
     *            每次创建Listener的时候需要把spring管理的类传进来"
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();  // ⚠️ 处理最后一批
        log.info("所有数据解析完成!");
    }

    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        demoDAO.save(cachedDataList);
        log.info("存储数据库成功!");
    }
}

// 持久层(官方原文:"如果是mybatis,尽量别直接调用多次insert,
//            自己写一个mapper里面新增一个方法batchInsert")
public class DemoDAO {
    public void save(List<DemoData> list) {
        // mybatis batchInsert
    }
}
java
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
    /**
     * 官方原文:"DemoDataListener 不能被spring管理,要每次读取excel都要new"
     */
    private static final int BATCH_COUNT = 100;
    private List<DemoData> cachedDataList =
        ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    private DemoDAO demoDAO;

    public DemoDataListener() {
        demoDAO = new DemoDAO();
    }

    /**
     * 官方原文:"如果使用了spring,请使用这个构造方法。
     *            每次创建Listener的时候需要把spring管理的类传进来"
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();  // ⚠️ 处理最后一批
        log.info("所有数据解析完成!");
    }

    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        demoDAO.save(cachedDataList);
        log.info("存储数据库成功!");
    }
}

// 持久层(官方原文:"如果是mybatis,尽量别直接调用多次insert,
//            自己写一个mapper里面新增一个方法batchInsert")
public class DemoDAO {
    public void save(List<DemoData> list) {
        // mybatis batchInsert
    }
}

Reference Library(按需加载)

参考库(按需加载)

详细代码与注释已拆分到
references/
。按场景加载:
详细代码与注释已拆分到
references/
。按场景加载:

API & 基础模式

API 与基础模式

  • references/api/4-read-modes.md — 4 种基础读取模式(最简 / 列下标 / 多 Sheet / 日期数字转换)
  • references/api/key-classes.md — Key Classes / Methods 速查
  • references/api/4-read-modes.md — 4 种基础读取模式(最简 / 列下标 / 多 Sheet / 日期数字转换)
  • references/api/key-classes.md — 关键类/方法速查

场景化(多行头 / 同步 / 表头 / 额外信息 / 公式 / 异常 / 不创建对象 / Web 上传)

场景化(多行头 / 同步 / 表头 / 额外信息 / 公式 / 异常 / 不创建对象 / Web 上传)

  • references/scenarios/multi-header-sync-header-extra.md — 多行头、同步读取、表头数据、额外信息
  • references/scenarios/formula-celltype-exception-noobj.md — 公式 / 单元格类型 / 异常处理 / 不创建对象
  • references/scenarios/web-upload.md — Web 上传读取
  • references/scenarios/multi-header-sync-header-extra.md — 多行头、同步读取、表头数据、额外信息
  • references/scenarios/formula-celltype-exception-noobj.md — 公式 / 单元格类型 / 异常处理 / 不创建对象
  • references/scenarios/web-upload.md — Web 上传读取

故障排查

故障排查

  • references/troubleshooting/gotchas.md — 12 条常见错误与正确做法
  • references/troubleshooting/gotchas.md — 12 条常见错误与正确做法

References

参考资料