EasyExcel写入多Sheet页

EasyExcel写入多Sheet页:分组数据的高效处理方案

在实际开发中,经常需要按分组(如城市、部门、日期)将数据分别写入 Excel 的不同 Sheet 页,既便于数据归类,也方便用户查看。EasyExcel 作为阿里巴巴开源的高效 Excel 处理工具,提供了简洁的 API 支持多 Sheet 写入。本文将详细讲解多 Sheet 写入的实现步骤、核心代码及优化技巧,以 “按城市分组写入” 为例展开说明。

核心原理

EasyExcel 写入多 Sheet 页的核心逻辑是:

  1. 创建一个全局的 ExcelWriter 对象,关联目标文件和数据模型(实体类);
  2. 为每个分组创建一个 WriteSheet 对象(指定 Sheet 索引和名称);
  3. 通过 ExcelWriter 向每个 WriteSheet 写入对应分组的数据;
  4. 所有 Sheet 写入完成后,调用 finish() 方法关闭资源,完成文件生成。

环境准备

依赖配置

在 Maven 项目的 pom.xml 中引入 EasyExcel 依赖:

<dependency>  
    <groupId>com.alibaba</groupId>  
    <artifactId>easyexcel</artifactId>  
    <version>3.3.0</version> <!-- 推荐使用最新稳定版 -->  
</dependency>

完整实现步骤

以 “按城市分组,每个城市的数据写入单独 Sheet 页” 为例,具体步骤如下:

定义数据模型(实体类)

创建与 Excel 列对应的实体类,使用 @ExcelProperty 注解指定列名和顺序:

import com.alibaba.excel.annotation.ExcelProperty;  
import lombok.Data;  

@Data  
public class CityData {  
    @ExcelProperty(value = "用户ID", index = 0)  // index 指定列顺序(0 开始)  
    private Long userId;  

    @ExcelProperty(value = "姓名", index = 1)  
    private String userName;  

    @ExcelProperty(value = "订单金额", index = 2)  
    private BigDecimal orderAmount;  

    // 其他字段...  
}

分组数据写入多 Sheet 页

假设已通过业务逻辑获取到 “城市 ID - 数据列表” 的映射关系,按城市循环创建 Sheet 并写入数据:

import com.alibaba.excel.EasyExcel;  
import com.alibaba.excel.ExcelWriter;  
import com.alibaba.excel.write.metadata.WriteSheet;  
import java.util.List;  
import java.util.Map;  

public class MultiSheetWriter {  

    /**  
     * 按城市分组写入多 Sheet 页  
     * @param filePath 目标 Excel 文件路径(如 "D:/城市数据.xlsx")  
     * @param cityDataMap 城市 ID 与对应数据的映射(key:城市ID,value:该城市的数据列表)  
     * @param cityNameMap 城市 ID 与城市名称的映射(用于设置 Sheet 名称)  
     */  
    public static void writeByCity(String filePath,  
                                 Map<Long, List<CityData>> cityDataMap,  
                                 Map<Long, String> cityNameMap) {  
        // 1. 创建 ExcelWriter 对象,关联文件路径和数据模型  
        ExcelWriter excelWriter = EasyExcel.write(filePath, CityData.class).build();  

        try {  
            int sheetIndex = 0; // Sheet 索引(从 0 开始)  
            // 2. 遍历每个城市,创建 Sheet 并写入数据  
            for (Map.Entry<Long, List<CityData>> entry : cityDataMap.entrySet()) {  
                Long cityId = entry.getKey();  
                List<CityData> cityDataList = entry.getValue();  
                String cityName = cityNameMap.getOrDefault(cityId, "未知城市"); // Sheet 名称为城市名  

                // 3. 创建 WriteSheet(指定索引和名称)  
                WriteSheet writeSheet = EasyExcel.writerSheet(sheetIndex, cityName).build();  

                // 4. 向当前 Sheet 写入数据  
                excelWriter.write(cityDataList, writeSheet);  

                sheetIndex++; // 索引自增,用于下一个 Sheet  
            }  
        } finally {  
            // 5. 所有 Sheet 写入完成后,关闭资源  
            if (excelWriter != null) {  
                excelWriter.finish();  
            }  
        }  

        System.out.println("多 Sheet 写入完成,文件路径:" + filePath);  
    }  

    // 测试方法  
    public static void main(String[] args) {  
        // 模拟数据:城市 ID -> 数据列表(实际项目中从数据库或业务逻辑获取)  
        Map<Long, List<CityData>> cityDataMap = buildCityData();  
        // 模拟城市名称映射  
        Map<Long, String> cityNameMap = Map.of(  
            1L, "北京",  
            2L, "上海",  
            3L, "广州"  
        );  

        // 调用多 Sheet 写入方法  
        writeByCity("D:/城市订单数据.xlsx", cityDataMap, cityNameMap);  
    }  

    // 模拟构建城市数据(实际项目中替换为业务逻辑)  
    private static Map<Long, List<CityData>> buildCityData() {  
        // ... 省略数据构建逻辑 ...  
        return Map.of(  
            1L, List.of(new CityData(1L, "张三", new BigDecimal("100.00")), ...),  
            2L, List.of(new CityData(2L, "李四", new BigDecimal("200.00")), ...),  
            3L, List.of(new CityData(3L, "王五", new BigDecimal("300.00")), ...)  
        );  
    }  
}

关键 API 解析

ExcelWriter

  • 作用:全局写入器,负责管理整个 Excel 文件的生命周期(创建、写入、关闭)。

  • 创建方式

    // 方式1:指定文件路径和数据模型  
    ExcelWriter writer = EasyExcel.write(filePath, CityData.class).build();  
    
    // 方式2:指定输出流(如网络响应流)  
    ExcelWriter writer = EasyExcel.write(response.getOutputStream(), CityData.class).build();

WriteSheet

  • 作用:代表一个 Sheet 页,包含索引、名称等元信息。

  • 创建方式

    // 参数1:Sheet 索引(0 开始,决定 Sheet 在 Excel 中的顺序)  
    // 参数2:Sheet 名称(显示在底部标签栏)  
    WriteSheet sheet = EasyExcel.writerSheet(0, "北京数据").build();

写入与关闭

  • 写入数据excelWriter.write(dataList, writeSheet),支持批量写入 List 数据;
  • 关闭资源excelWriter.finish() 必须在所有 Sheet 写入完成后调用,否则文件可能损坏或数据丢失。

扩展技巧

1. 自定义 Sheet 样式

通过 WriteSheetregisterWriteHandler 方法添加样式处理器,自定义表头、内容单元格样式:

import com.alibaba.excel.write.handler.WriteHandler;  
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;  

// 创建 Sheet 时添加样式处理器(自动调整列宽)  
WriteSheet writeSheet = EasyExcel.writerSheet(sheetIndex, cityName)  
    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动列宽  
    .build();

2. 动态数据模型(无实体类)

若数据结构不固定,可使用 List<List<String>> 作为数据模型(外层 List 代表行,内层 List 代表列):

// 无实体类时,写入方法如下  
ExcelWriter writer = EasyExcel.write(filePath).build();  
WriteSheet sheet = EasyExcel.writerSheet(0, "动态数据").build();  
List<List<String>> data = List.of(  
    List.of("用户ID", "姓名", "金额"), // 表头  
    List.of("1", "张三", "100"),     // 数据行  
    List.of("2", "李四", "200")  
);  
writer.write(data, sheet);  
writer.finish();

3. 大数据量分批写入

若单个 Sheet 的数据量过大(如 10 万行),可分批次写入,避免内存溢出:

// 假设 cityDataList 是超大列表,分 1000 条/批写入  
List<CityData> cityDataList = ...;  
int batchSize = 1000;  
for (int i = 0; i < cityDataList.size(); i += batchSize) {  
    int end = Math.min(i + batchSize, cityDataList.size());  
    List<CityData> batch = cityDataList.subList(i, end);  
    excelWriter.write(batch, writeSheet); // 分批写入同一 Sheet  
}

4. 不同 Sheet 对应不同实体类

若不同 Sheet 的数据结构不同(如 “用户表” 和 “订单表”),可在创建 WriteSheet 时指定实体类:

// 第一个 Sheet 用 User 实体  
WriteSheet userSheet = EasyExcel.writerSheet(0, "用户数据")  
    .head(User.class) // 指定当前 Sheet 的数据模型  
    .build();  

// 第二个 Sheet 用 Order 实体  
WriteSheet orderSheet = EasyExcel.writerSheet(1, "订单数据")  
    .head(Order.class)  
    .build();  

excelWriter.write(userList, userSheet);  
excelWriter.write(orderList, orderSheet);

常见问题

1. Sheet 名称重复

  • 现象:多个 Sheet 名称相同时,Excel 会自动在名称后加序号(如 “北京”“北京 (2)”);
  • 解决:确保 cityNameMap 中的城市名称唯一,或在写入前检查并处理重复名称。

2. 数据写入不完整

  • 原因:未调用 excelWriter.finish() 或中途抛出异常导致资源未关闭;
  • 解决:将 finish() 放在 finally 块中,确保无论是否异常都会执行。

3. 内存溢出(OOM)

  • 原因:单个 Sheet 数据量过大,一次性加载到内存;
  • 解决:采用分批写入(见 “扩展技巧 3”),或使用 EasyExcel 的 SAX 模式(事件驱动)处理超大文件。