0%

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 依赖:

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

完整实现步骤

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

定义数据模型(实体类)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 并写入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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
    2
    3
    4
    5
    // 方式1:指定文件路径和数据模型  
    ExcelWriter writer = EasyExcel.write(filePath, CityData.class).build();

    // 方式2:指定输出流(如网络响应流)
    ExcelWriter writer = EasyExcel.write(response.getOutputStream(), CityData.class).build();

WriteSheet

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

  • 创建方式

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

写入与关闭

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

扩展技巧

1. 自定义 Sheet 样式

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

1
2
3
4
5
6
7
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 代表列):

1
2
3
4
5
6
7
8
9
10
// 无实体类时,写入方法如下  
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 万行),可分批次写入,避免内存溢出:

1
2
3
4
5
6
7
8
// 假设 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 时指定实体类:

1
2
3
4
5
6
7
8
9
10
11
12
// 第一个 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 模式(事件驱动)处理超大文件。

欢迎关注我的其它发布渠道