# bingExcel **Repository Path**: bingyulei007/bingExcel ## Basic Information - **Project Name**: bingExcel - **Description**: 用于excel与javaBean转换的orm框架,旨在简化excel读取和写出,使普通开发人员通过几行代码就可以读取或者生成excel文件。另外处理掉excel大数据读写时候的问题 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: develop - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 140 - **Forks**: 55 - **Created**: 2016-04-29 - **Last Updated**: 2026-07-03 ## Categories & Tags **Categories**: excel-utils **Tags**: None ## README # BingExcel [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html) BingExcel 是一个基于 Apache POI 的轻量级 Java Excel 工具库,用注解或 Builder 配置完成 Excel(`.xls` / `.xlsx` / CSV)与 Java 实体对象之间的读写转换。 它适合以下场景: - 将 Excel 数据读取为 Java Bean 列表; - 将 Java Bean 列表导出为 Excel 或 CSV; - 通过表头名称读取列顺序不固定的 Excel; - 为字段定制类型转换逻辑; - 读写多个 Sheet,或使用底层 Handler 做更细粒度控制。 ## 目录 - [特性](#特性) - [环境要求](#环境要求) - [安装](#安装) - [快速开始](#快速开始) - [核心注解](#核心注解) - [读取 Excel](#读取-excel) - [写出 Excel](#写出-excel) - [写出 CSV](#写出-csv) - [自定义转换器](#自定义转换器) - [运行时映射配置](#运行时映射配置) - [配置优先级](#配置优先级) - [底层 Handler API](#底层-handler-api) - [常见问题](#常见问题) - [作者与许可](#作者与许可) - [更新日志](#更新日志) ## 特性 - 支持 `.xls` 与 `.xlsx` 格式读写; - 支持 CSV 导出,可选择分隔符、表头与 BOM; - 注解驱动字段映射,代码侵入低; - 读取时支持按列索引或表头名称定位字段; - 支持字段级、类型级、内置默认三级转换器; - 支持多 Sheet 读取与多 Sheet 写出; - 支持 Builder 方式在运行时注册字段映射与转换器。 ## 环境要求 | 组件 | 版本 | | --- | --- | | JDK | 17+ | | Apache POI | 5.4.1 | | commons-lang3 | 3.18.0 | | commons-csv | 1.11.0(optional) | | xercesImpl | 2.12.2 | | JUnit(测试) | 4.13.2 | ## 安装 本项目不再发布到 Maven 中央仓库,请在本地构建安装: ```bash mvn clean install ``` 这会将 `cn.com.binging:excel:5.0-SNAPSHOT` 安装到本地 `~/.m2` 仓库,随后可在本地其他工程中引用: ```xml cn.com.binging excel 5.0-SNAPSHOT ``` > 如需跳过测试加快安装:`mvn clean install -DskipTests`。 ## 快速开始 ### 1. 定义实体类 使用 `@CellConfig` 声明 Java 字段与 Excel 列的映射关系;使用 `@OutAlias` 声明导出时的 Sheet 名称。 ```java import com.bing.excel.annotation.CellConfig; import com.bing.excel.annotation.OutAlias; @OutAlias("人员信息") public class Person { @CellConfig(index = 0, aliasName = "姓名") private String name; @CellConfig(index = 1, aliasName = "年龄") private Integer age; @CellConfig(index = 2, aliasName = "薪资") private Double salary; // getter / setter 省略 } ``` ### 2. 读取 Excel ```java import com.bing.excel.core.BingExcel; import com.bing.excel.core.BingExcelBuilder; import com.bing.excel.core.impl.BingExcelImpl.SheetVo; import java.io.File; BingExcel bingExcel = BingExcelBuilder.builderInstance(); // 从第 2 行开始读取:行索引从 0 开始,因此 1 表示跳过第 1 行表头 SheetVo sheet = bingExcel.readFile(new File("person.xlsx"), Person.class, 1); System.out.println(sheet.getSheetName()); System.out.println(sheet.getObjectList()); ``` ### 3. 写出 Excel ```java import com.bing.excel.core.BingExcel; import com.bing.excel.core.BingExcelBuilder; import java.util.Arrays; import java.util.List; BingExcel bingExcel = BingExcelBuilder.builderInstance(); List people = Arrays.asList( new Person("张三", 25, 10000.0), new Person("李四", 30, 15000.0) ); bingExcel.writeXlsx("output.xlsx", people); ``` ## 核心注解 ### `@CellConfig` 标注在字段上,描述字段与 Excel 列的关系。 | 属性 | 默认值 | 说明 | | --- | --- | --- | | `index` | `-1` | Excel 列索引,从 0 开始。写出 Excel / CSV 时必须 `>= 0`;读取时如果为 `-1` 且配置了 `aliasName`,会按表头匹配列。 | | `aliasName` | `""` | 表头名称。读取时可用于表头匹配;写出时作为列标题。为空时通常使用字段名。 | | `readRequired` | `false` | 读取时该字段是否必填。 | ```java public class User { @CellConfig(index = 0, aliasName = "姓名") private String name; @CellConfig(index = 1, aliasName = "年龄", readRequired = true) private Integer age; } ``` ### `@OutAlias` 标注在类上,指定写出 Excel 时的 Sheet 名称。 ```java @OutAlias("销售数据") public class SaleRecord { @CellConfig(index = 0, aliasName = "商品") private String product; } ``` ### `@BingConvertor` 标注在字段上,为单个字段指定转换器。 ```java import com.bing.excel.annotation.BingConvertor; import com.bing.excel.converter.base.BooleanFieldConverter; public class User { @CellConfig(index = 0, aliasName = "是否启用") @BingConvertor(value = BooleanFieldConverter.class, strings = {"是", "否"}, booleans = {true}) private Boolean active; } ``` ## 读取 Excel ### 读取单个 Sheet ```java // 读取第一个 Sheet,从第 2 行开始 SheetVo sheet = bingExcel.readFile(file, Person.class, 1); ``` 使用 `ReaderCondition` 可以控制 Sheet、起始行和结束行: ```java import com.bing.excel.core.ReaderCondition; ReaderCondition condition = new ReaderCondition<>(0, Person.class) .setStartRow(1) .setEndRow(100); SheetVo sheet = bingExcel.readFile(file, condition); ``` ### 按表头名称匹配列 当 Excel 列顺序不稳定时,可以只配置 `aliasName`,不配置 `index`: ```java public class User { @CellConfig(aliasName = "姓名") private String name; @CellConfig(aliasName = "年龄") private Integer age; @CellConfig(aliasName = "薪资") private Double salary; } ``` 注意: - 该能力仅用于读取;写出 Excel / CSV 时字段必须有合法的 `index`; - 表头匹配时会自动去除前后空白并忽略大小写,例如表头 ` Name ` 或 `name` 都能匹配 `aliasName = "Name"`; - 表头行必须位于 `startRow - 1`:常见用法是第 1 行为表头、`startRow = 1` 从第 2 行开始读取; - 表头匹配失败时:如果字段的 `readRequired = false`(默认),跳过该字段、值保持 `null`;如果 `readRequired = true`,抛出 `IllegalCellConfigException`,异常信息中包含可用表头,便于排查; ### 读取多个 Sheet ```java ReaderCondition[] conditions = new ReaderCondition[] { new ReaderCondition<>(0, Person.class).setStartRow(1), new ReaderCondition<>(1, Department.class).setStartRow(1) }; List sheets = bingExcel.readFileToList(file, conditions); ``` ### 从 `InputStream` 读取 ```java try (InputStream input = new FileInputStream("person.xlsx")) { SheetVo sheet = bingExcel.readStream(input, Person.class, 1); } ``` ## 写出 Excel ### 写出单个 Sheet ```java bingExcel.writeXlsx("output.xlsx", people); // xlsx bingExcel.writeXls("output.xls", people); // xls ``` 也可以写入 `OutputStream`: ```java try (OutputStream output = new FileOutputStream("output.xlsx")) { bingExcel.writeXlsx(output, people); } ``` ### 写出多个 Sheet 直接传入多个集合时,每个集合会写入一个 Sheet: ```java bingExcel.writeXlsx("company.xlsx", people, departments); ``` 需要为不同 Sheet 指定名称或列表时,可以使用 `SheetExcel`: ```java import com.bing.excel.core.impl.BingExcelImpl.SheetExcel; SheetExcel peopleSheet = new SheetExcel(); peopleSheet.setSheetName("人员"); peopleSheet.setList(people); SheetExcel departmentSheet = new SheetExcel(); departmentSheet.setSheetName("部门"); departmentSheet.setList(departments); bingExcel.writeXlsx("company.xlsx", peopleSheet, departmentSheet); // 如需写出 .xls:bingExcel.writeXls("company.xls", peopleSheet, departmentSheet); ``` ### 常用写出 API | 方法 | 说明 | | --- | --- | | `writeXlsx(String path, Iterable... iterables)` | 写出 `.xlsx` 文件,每个集合写入一个 Sheet。 | | `writeXls(String path, Iterable... iterables)` | 写出 `.xls` 文件,每个集合写入一个 Sheet。 | | `writeXlsx(OutputStream stream, Iterable... iterables)` | 写出 `.xlsx` 到输出流。 | | `writeXls(OutputStream stream, Iterable... iterables)` | 写出 `.xls` 到输出流。 | | `writeXlsx(String path, SheetExcel... sheetExcels)` | 按 `SheetExcel` 描述写出多个 Sheet 到 `.xlsx` 文件。 | | `writeXls(String path, SheetExcel... sheetExcels)` | 按 `SheetExcel` 描述写出多个 Sheet 到 `.xls` 文件。 | ## 写出 CSV ### 基础用法 ```java bingExcel.writeCsv("output.csv", people); ``` ### 自定义分隔符、表头和 BOM ```java try (OutputStream output = new FileOutputStream("output.csv")) { bingExcel.writeCsv(output, people, ',', true, true); } ``` 参数含义: | 参数 | 说明 | | --- | --- | | `output` | 输出流。 | | `people` | 待写出的对象集合。 | | `','` | CSV 分隔符。 | | `true` | 是否写出表头。 | | `true` | 是否写入 UTF-8 BOM;用 Excel 打开中文 CSV 时建议开启。 | ## 自定义转换器 转换器用于在 Excel 单元格文本与 Java 字段值之间做双向转换。 ### 实现转换器 ```java import com.bing.excel.converter.AbstractFieldConvertor; import com.bing.excel.core.handler.ConverterHandler; import com.bing.excel.vo.OutValue; import java.lang.reflect.Type; import java.time.LocalDate; import java.time.format.DateTimeFormatter; public class LocalDateConverter extends AbstractFieldConvertor { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @Override public boolean canConvert(Class clz) { return LocalDate.class.equals(clz); } @Override public Object fromString(String cell, ConverterHandler converterHandler, Type targetType) { if (cell == null || cell.trim().isEmpty()) { return null; } return LocalDate.parse(cell, FORMATTER); } @Override public OutValue toObject(Object source, ConverterHandler converterHandler) { if (source == null) { return OutValue.stringValue(""); } return OutValue.stringValue(((LocalDate) source).format(FORMATTER)); } } ``` ### 按类型全局注册 ```java BingExcel bingExcel = BingExcelBuilder.toBuilder() .registerFieldConverter(LocalDate.class, new LocalDateConverter()) .build(); ``` ### 内置转换器 | 转换器 | 支持类型 | | --- | --- | | `BooleanFieldConverter` | `boolean`、`Boolean` | | `ByteFieldConverter` | `byte`、`Byte` | | `CharacterFieldConverter` | `char`、`Character` | | `DateFieldConverter` | `Date`、`java.sql.Date` | | `DoubleFieldConverter` | `double`、`Double` | | `FloatFieldConverter` | `float`、`Float` | | `IntegerFieldConverter` | `int`、`Integer` | | `LongFieldConverter` | `long`、`Long` | | `ShortFieldConverter` | `short`、`Short` | | `StringFieldConverter` | `String` | | `EnumConVerter` | 枚举类型 | | `ArrayConverter` | 数组类型 | | `CollectionConverter` | 集合类型 | ## 运行时映射配置 如果不能修改实体类源码,或希望按场景动态配置字段映射,可以使用 Builder 注册字段映射: ```java BingExcel bingExcel = BingExcelBuilder.toBuilder() .addFieldConversionMapper(Person.class, "name", 0, "姓名") .addFieldConversionMapper(Person.class, "age", 1, "年龄") .addFieldConversionMapper(Person.class, "salary", 2, "薪资") .build(); ``` 也可以为字段级别指定转换器: ```java BingExcel bingExcel = BingExcelBuilder.toBuilder() .addFieldConversionMapper(Person.class, "birthday", 3, "生日", new LocalDateConverter()) .build(); ``` 建议同一个字段只选择一种配置方式:要么使用注解,要么使用 Builder。对于同一个实体类,也建议保持配置方式一致,避免阅读和维护成本。 ## 配置优先级 BingExcel 的映射和转换器配置由三条独立规则决定。 ### 1. 字段映射来源:Builder 高于注解 同一个字段同时存在 `@CellConfig` / `@BingConvertor` 与 `addFieldConversionMapper(...)` 时,Builder 注册的字段映射优先。 需要特别注意:Builder 是“整字段替换”,不是“按属性合并”。一旦某个字段通过 `addFieldConversionMapper(...)` 注册,字段上的注解映射会被整体屏蔽,未传入的属性不会回退到注解值。 > 以上优先级适用于 `BingExcel` 的 `readFile` / `readStream` 等读取接口。底层事件模型 `BingExcelEvent` 仅使用注解映射,不会应用 Builder 通过 `addFieldConversionMapper(...)` 注册的字段映射;需要 Builder 映射生效时请使用 `BingExcel` 的常规读取方法。 ### 2. 列定位:显式 `index` 高于 `aliasName` 读取时: 1. 如果 `index >= 0`,直接按列索引读取; 2. 如果 `index < 0` 且 `aliasName` 非空,按表头名称匹配列。 写出时: - 必须提供合法的 `index`; - `aliasName` 只作为表头文本。 ### 3. 转换器:字段级高于类型级,高于内置默认 优先级从高到低: 1. 字段级转换器:`@BingConvertor` 或 `addFieldConversionMapper(..., converter)`; 2. 类型级转换器:`registerFieldConverter(type, converter)`; 3. 内置默认转换器。 ## 底层 Handler API 当注解和 Builder 无法满足需求时,可以使用底层读写 Handler。例如:需要手动写表头、控制每一行内容、设置下拉框数据有效性等。 ```java import com.bing.excel.writer.ExcelWriterFactory; import com.bing.excel.writer.handler.WriteHandler; import com.bing.excel.vo.ListLine; WriteHandler handler = ExcelWriterFactory.createXSSF("template.xlsx"); handler.createSheet("数据"); handler.writeHeader(new ListLine() .addValue(0, "姓名") .addValue(1, "性别") .addValue(2, "年龄")); handler.writeLine(new ListLine() .addValue(0, "张三") .addValue(1, "男") .addValue(2, 25)); handler.setDataValidationList((short) 1, (short) 1000, (short) 1, (short) 1, new String[] {"男", "女", "其他"}); handler.flush(); ``` 常用工厂方法: ```java WriteHandler xlsxHandler = ExcelWriterFactory.createXSSF("output.xlsx"); WriteHandler xlsHandler = ExcelWriterFactory.createHSSF("output.xls"); ``` ## 常见问题 ### Q1:如何跳过表头? 行索引从 0 开始。如果第 1 行是表头,从第 2 行开始读取时传入 `startRow = 1`: ```java SheetVo sheet = bingExcel.readFile(file, Person.class, 1); ``` ### Q2:列顺序会变,怎么读取? 使用 `@CellConfig(aliasName = "表头名称")`,并且不要配置 `index`。读取时框架会通过表头文本定位列。 ### Q3:为什么写出时报 `index < 0` 相关异常? `aliasName` 表头匹配只支持读取。写出 Excel / CSV 时必须为每个要写出的字段提供 `index >= 0` 的列索引。 ### Q4:如何处理空值? 建议实体字段使用包装类型,例如 `Integer`、`Double`、`Boolean`。基本类型无法表达 `null`,空单元格会落到类型默认值或转换器处理逻辑。 ### Q5:用 Excel 打开 CSV 中文乱码怎么办? 写出 CSV 时开启 BOM: ```java bingExcel.writeCsv(output, people, ',', true, true); ``` ### Q6:注解和 Builder 可以混用吗? 不建议对同一个字段混用。Builder 注册字段映射后会整体替换该字段的注解映射,字段上的 `@BingConvertor` 也不会自动继承。 ## 作者与许可 - 作者:shizhongtao(bingyulei008@gmail.com) - 版权:Copyright 2015 http://bingExcel.svend.cc - 许可协议:本项目基于 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) 开源发布,完整许可条款请参见 [LICENCE](LICENCE)。 ## 更新日志 ### v5.0-SNAPSHOT(未发布) - 升级 JDK 基线至 17; - Apache POI 升级至 5.4.1,并适配 4→5 的 API 变更; - commons-lang3 升级至 3.18.0、commons-csv 升级至 1.11.0; - 移除 Guava 依赖,缓存改用 `ConcurrentHashMap`,工具类改用 JDK 与 commons-lang3; - 移除 Caffeine 与 log4j-core 依赖,主代码统一使用 `java.util.logging`; - commons-csv 标记为 `optional`,使用 `writeCsv` 时由调用方自行引入; - 写出 API 重命名:`writeExcel`→`writeXlsx`、`writeOldExcel`→`writeXls`,并新增多 Sheet `writeXlsx` / `writeXls` 重载; - CSV 写出 API 统一为 camelCase 命名 `writeCsv`; - 修复 `writeXlsx(OutputStream)` 误用 `HSSFWorkbook` 导致实际写出 `.xls` 的问题; - 不再发布到 Maven 中央仓库,改为本地 `mvn install` 安装。 ### v4.1(未发布) - 支持 `@CellConfig(aliasName = "...")` 通过表头匹配列(仅读取方向); - 表头匹配时自动去除前后空白并忽略大小写; - 读取时若按表头匹配的字段未能解析出列索引(如表头行缺失),改为抛出 `IllegalCellConfigException`,不再以 `ArrayIndexOutOfBoundsException` 形式暴露; - 统一说明并修正注解映射、Builder 映射和转换器之间的优先级; - 写出 Excel / CSV 时遇到非法字段索引会抛出 `IllegalCellConfigException`,避免负索引继续向下传递。 ### v4.0 - 升级依赖版本; - 启用 Maven Central 发布配置; - 支持快照版本发布。 ### v3.0 - POI 升级至 4.1.2; - commons-lang3 升级至 3.14.0; - commons-csv 升级至 1.10.0; - JUnit 升级至 4.13.2。 ### v2.4 - 修复 SAX 模式读取稳定性问题。 ### v2.3 - 新增多 Sheet 导出功能。 ### v2.2 - 新增 CSV 导出支持。