EasyExcel 使用教程


Java 中有两个较为常用的 Excel 类库,分别是 ApachePOI 以及 AlibabaEasyExcel,其中 EasyExcel 引入了事件驱动模型在内存占用方面有着优秀的表现。

在这篇文章中将介绍如何通过 EasyExcelExcel 文件进行读写操作。

一、数据读取

1. 基础准备

首先先准备一个 Excel 数据文件,文件内容如下:

id    name    gender    birthday
1    User-1    Male    2024-01-29
2    User-2    Male    2024-01-29
3    User-3    Male    2024-01-29
4    User-4    Male    2024-01-29

完成后为上述的数据创建对应的实体类对象,这里略去 getset 方法部分内容。

public class User {

    private String id;

    private String name;

    private String gender;

    private Date birthday;
}

2. 数据读取

读取数据的方式十分简单,通过 EasyExcel 对象即可创建读取对象,其可配置的内容参考下表。

方法 作用
read() 读取的对象,传入类型可为 File, IO 或 文件路径。
head() 指定 Excel 列,当为类时则与类属性名称一一对应。
sheet() 指定 Sheet 页,默认为第一页,可通过名称或下标指定。
headRowNumber() 指定文件表头所在的行数,默认为 1。

下述通过代码示例读取刚才准备的 Excel 文件,内容如下:

public class ExcelReadTest {

    public void read() {
        String fileLocate = "src\\main\\resources\\data.xls";
        try (InputStream in = Files.newInputStream(Paths.get(fileLocate))) {
            List<User> list = EasyExcel.read(in)
                    .head(User.class)
                    .sheet()
                    .doReadSync();
            System.out.println(list);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

需注意的是在使用 head(xxx.class) 时默认数据字段映射关系是与类属性定义顺序一致的。

以刚才创建的 User 类为例,Excel 文件中列的定义顺序为 id, name, gender, birthday,则在赋值时其默认实体类中第一个属性即为 id 并以此类推。如果此时 User 的属性定义顺序为 name, id 那么最终读取的数据 id 值将会被赋值给 name 字段。

3. 动态读取

EasyExcel 除了支持实体类映射外还可以动态指定列名实现读取。

head() 中通过集合动态指定文件中的列名,其返回的结果为 List<LinkedMap<Integer, Object>>,其中每一个 LinkMap 对应文件内的一行数据,且 Key 为对应列的下表,Value 为对应单元格的数据。

public void read() {
    String fileLocate = "src\\main\\resources\\data.xls";
    try (InputStream in = Files.newInputStream(Paths.get(fileLocate))) {
        // 定义列名
        List<List<String>> heads = Stream.of("id", "name", "gender", "birthday")
                .map(Arrays::asList)
                .collect(Collectors.toList());
        List<LinkedMap<Integer, Object>> list = EasyExcel.read(in)
                .head(heads)
                .sheet()
                .doReadSync();

        System.out.println(list);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

二、数据写入

1. 写入示例

EasyExcel 写入时支持多种格式,其中的 excelType() 用于指定文件类型。可选值有下述三类,对应三种不同的数据文件格式。

ExcelTypeEnum.CSV
ExcelTypeEnum.XLS
ExcelTypeEnum.XLSX

数据写入的方式与之前的读取示例类似,不同之处仅为之前的 read()doReadSync() 替换为 write()doWrite(),其余的属性配置不变,示例代码如下:

public void demo1() {
    List<User> dataList = new ArrayList<>();
    for (int i = 1; i < 5; i++) {
        dataList.add(new User(i + "", "User-" + i, "Male", new Date()));
    }

    String fileLocate = "src\\main\\resources\\data.xls";
    EasyExcel.write(fileLocate)
            .excelType(ExcelTypeEnum.XLS)
            .head(User.class)
            .sheet("Test-data")
            .doWrite(data);
}

2. Sheet管理

当需要写入多个 Sheet 到同一个 Excel 文件时,即可使用 ExcelWriterSheetBuilder

如下述示例中即创建两个 Sheet 页数据,Sheet 页名称分别为 Sheet-1Sheet-2

 public void demo2() throws IOException {
    List<User> dataList = new ArrayList<>();
    for (int i = 1; i < 5; i++) {
        dataList.add(new User(i + "", "User-" + i, "Male", new Date()));
    }

    String fileLocate = "src\\main\\resources\\info.xls";
    // 创建 ExcelWriter 对象
    ExcelWriter excelWriter = EasyExcel.write(fileLocate)
            .excelType(ExcelTypeEnum.XLS)
            .build();
    for (int i = 1; i <= 2; i++) {
        // 创建 Sheet 对象并写入数据
        ExcelWriterSheetBuilder sheetBuilder = new ExcelWriterSheetBuilder(excelWriter);
        sheetBuilder.sheetName("Sheet-" + i);
        sheetBuilder.head(User.class);
        sheetBuilder.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
        // 将 sheet 页写入 excel 中
        excelWriter.write(dataList, sheetBuilder.build());
    }
    // 写入完成
    excelWriter.finish();
}

3. 动态写入

在上述的写入示例中,文件的数据的写入和列名的定义都是通过 Java 实体对象映射实现。而 EasyExcel 也提供了动态的写入方式,通过 List 集合传入可变对象,由此即可为系统设计一套通用的数据写入模块。

在动态写入中,Excel 文件的列名通过 List<List<String>> 格式数据指定,而数据的载体是 List<List<Object>> 而非动态读取示例中的 List<Map>

// 动态读取返回的数据格式
// List<Map<String, Object>>
[
    { 
        "id": "1", 
        "name": "user-1", 
        "gender": "Male", 
        "birthday": "2023-01-01 00:00:01"
    }, { 
        "id": "2", 
        "name": "user-2", 
        "gender": "Male", 
        "birthday": "2023-01-01 00:00:02"
    }
]

// 动态写入要求的数据格式
// List<List<Object>>
[
    ["1", "user-1", "Male", "2023-01-01 00:00:01"],
    ["2", "user-2", "Male", "2023-01-01 00:00:02"]
]

根据上述逻辑,其相对应的程序代码如下:

public void dynamicWrite() throws IOException {
    // 构建测试数据
    List<Map<String, Object>> dataList = new ArrayList<>();
    for (int i = 1; i < 5; i++) {
        Map<String, Object> map = Map.of(
                "id", i + "",
                "name", "user-" + i,
                "gender", "Male",
                "birthday", new Date()
        );
        dataList.add(map)
    }

    List<String> headList = List.of("id", "name", "gender", "birthday");
    // 转化 "List<Map>" 为 "List<List>"
    List<List<Object>> rowDatas = new ArrayList<>();
    for (Map<String, Object> map : dataList) {
        List<Object> row = new ArrayList<>();
        for (String name : headList) {
            row.add(map.get(name));
        }
        rowDatas.add(row);
    }
    // 声明 Excel 的表头
    List<List<String>> heads = headList.stream()
            .map(Arrays::asList)
            .collect(Collectors.toList());
    // 写入数据
    String fileLocate = "src\\main\\resources\\info.xls";
    EasyExcel.write(fileLocate)
            .excelType(ExcelTypeEnum.XLS)
            .head(heads)
            .sheet("Test-data")
            .doWrite(rowDatas);
}

三、注解使用

在上述的读写中我们采用默认的映射方式,属性声明顺序需和文件对应。但 EasyExcel 也提供了注解方式支持更灵活的配置,在更多的业务场景中也是基于此方式集成。

1. ExcelProperty

@ExcelProperty 注解可指定列与实体字段的映射关系,默认的字段的映射顺序是与属性的定义顺序相关。

默认生成的文件表头字段取相应的成员变量名称,可通过 value 配置别名,同时 order 可修改表头顺序。

属性 描述
value 定义表头,输入多个则合并单元格。
order 设置属性对应的顺序。

如下示例中则最后生成 Excel 文件中列名分别为 编号姓名,其中第一列为 编号,第二列为 姓名

public class ExcelUser {

    @ExcelProperty(value = "编号", order = 1)
    private String id;

    @ExcelProperty(value = "姓名", order = 2)
    private String name;
}

2. ExcelIgnore

@ExcelIgnore 用于指定需要忽略的属性,由于 EasyExcel 是与类的属性进行映射,若实体的属性字段数量与文件表头数量不一致导致映射失败。

因此,针对某些不相关的业务字段,常利用 @ExcelIgnore 标注为忽略,在写入文件时不会处理对应数据。

如下示例在读取或写入文件时将忽略 address 字段。

public class ExcelUser {

    private String id;

    @ExcelIgnore
    private Date address;
}

3. DateTimeFormat

@DateTimeFormat 用于指定读取数据的时间格式化形式。

如下示例在读取或写入文件时将会格式化字段 birthday 字段 值为 yyy-MM-dd 格式。

public class ExcelUser {

    private String id;

    @DateTimeFormat("yyy-MM-dd")
    private Date birthday;
}

四、读取监听

1. 基本介绍

EasyExcel 中执行数据的读取写入时,其也提供了监听器用于在读写过程中的操作。

监听器的声明方式十分简单,通过继承 AnalysisEventListener<> 接口即可,其针对数据的读取提供了一系统回调函数,由此我们便可实现更灵活的数据操作。

public class ReadListener<T> extends AnalysisEventListener<T> {

}

2. 事件监听

在继承上述接口后,需要重写下表中的事件方法,详细的事件描述参考下表:

方法 作用
invokeHeadMap() 读取 headRowNumber() 指定的表头信息。
invoke() 读取动作的触发器,每读取一行数据都将触发。
onException() 读取数据异常时的事件方法。
doAfterAllAnalysed() 文件内所有数据读取完成后的触发事件。

下述为一个完成的事件监听器定义示例,代码如下:

@Getter
public class ReadListener<T> extends AnalysisEventListener<T> {

    private final Class<T> tClass;

    private final List<T> dataList = new ArrayList<>();


    public ReadListener(Class<T> tClass){
        this.tClass = tClass;
    }

    /**
     * 文件列名表头读取,可执行内容格式校验
     *
     * @param headMap 文件表头
     * @param context 上下文对象
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
    }

    /**
     * 读取行数据的触发器,可执行数据校验
     *
     * @param row     当前行数据
     * @param context 上下文对象
     */
    @Override
    public void invoke(T row, AnalysisContext context) {
        dataList.add(row);
    }

    /**
     * 读取数据异常时的触发器
     *
     * @param exception 异常对象
     * @param context   上下文对象
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
    }

    /**
     * 所有数据读取完成后的触发事件
     *
     * @param context 上下文对象
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 可对结果执行初步预处理
        System.out.println("Listener after all: " + dataList);
    }
}

3. 应用示例

监听器的使用方式同样十分简单,以数据读取为例,在声明 read() 属性时传入即可。

如下述为监听器的使用示例,与之前的不同仅在于在 read() 中多了 listener 参数。

@Test
public void read() {
    String fileLocate = "src\\main\\resources\\data.xls";
    try (InputStream in = Files.newInputStream(Paths.get(fileLocate))) {
        // 创建监听器
        ReadListener<User> listener = new ReadListener<>(User.class);

        // 传入监听器并操作读取
        EasyExcel.read(in, listener)
                .head(User.class)
                .sheet()
                .doReadSync();
        
        // 读取结果
        System.out.println(listener.getDataList());
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

五、写入监听

1. 内容样式

在文件数据写入时,与读取监听器类似,EasyExcel 提供了对应 CellWriteHandler 可实现数据写入时的回调。

其集成方式并不复杂,通过实现 CellWriteHandler 接口重写 afterCellDispose(),在写入每个单元格数据后将会执行此回调事件。

如下述示例即通过 CellWriteHandler 实现表头每列首字符标红。

public class RedCellWriteHandler implements CellWriteHandler {

    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        Integer rowIndex = context.getRowIndex();
        Integer columnIndex = context.getColumnIndex();
        if (rowIndex < 0 || columnIndex > 0) {
            return;
        }
        Cell cell = context.getCell();
        String content = cell.getStringCellValue();
        if (StringUtils.isBlank(content)) {
            // 空值不处理
            return;
        }

        Workbook workbook = cell.getSheet().getWorkbook();
        // 设置字体
        Font font = workbook.createFont();
        font.setBold(true);
        font.setColor(IndexedColors.RED.getIndex());
        RichTextString richText = workbook.getCreationHelper().createRichTextString(content);
        richText.applyFont(0, 1, font);
        // 回写单元格
        cell.setCellValue(richText);
    }
}

2. 代码集成

针对声明的处理器,在写入时通过 registerWriteHandler() 方法注册。

仍以前文中的写入为例,其对应的集成代码如下,注意需添加 inMemory(true) 使之生效。

public void demo1() {
    List<User> dataList = new ArrayList<>();
    for (int i = 1; i < 5; i++) {
        dataList.add(new User(i + "", "User-" + i, "Male", new Date()));
    }

    String fileLocate = "src\\main\\resources\\data.xls";
    EasyExcel.write(fileLocate)
            .inMemory(true)
            // 注册处理器
            .registerWriteHandler(new RedCellWriteHandler())
            .head(User.class)
            .doWrite(data);
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录