主流方案对比
| 特性 | Apache POI | EasyExcel | JXL (已不推荐) |
|---|---|---|---|
| 维护方 | Apache 软件基金会 | 阿里巴巴 | - |
| 核心优势 | 功能最强大,支持所有 Excel 版本和复杂格式 | 内存占用极低,性能好,解决了 POI 的 OOM 问题 | 简单易用,但已停止更新多年 |
| 主要缺点 | 大数据量时容易产生 OOM (内存溢出) | 对于极其复杂的样式和图表支持不如 POI | 功能有限,不支持 .xlsx 格式,已过时 |
| 适用场景 | 需要处理复杂 Excel 样式、图表,或数据量可控的场景 | 大数据量导出(万行以上),追求高性能和低内存占用的场景 | 旧项目维护,或非常简单的导出需求(不推荐新项目使用) |
- 新项目首选:EasyExcel,它在绝大多数场景下都能完美胜任,并且解决了 POI 的最大痛点。
- 需要复杂功能:如果必须处理复杂的样式、公式、图表等,且数据量不大,可以使用 Apache POI。
- 避免使用:JXL,除非你维护的是非常古老的代码。
使用 EasyExcel (强烈推荐)
EasyExcel 是阿里巴巴开源的一个基于 Java 的、简单、省内存的 Excel 处理工具,它通过 SAX 模式(流式读取)来解析 Excel,大大降低了内存消耗,非常适合大数据量的导出和导入。

(图片来源网络,侵删)
添加 Maven 依赖
在你的 pom.xml 文件中添加 EasyExcel 的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version> <!-- 请使用最新版本 -->
</dependency>
准备数据模型
创建一个与 Excel 表头对应的 Java 实体类,使用 @ExcelProperty 注解来指定表头的名称。
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
// 使用 Lombok 简化代码,也可以手动写 getter/setter
@Data
public class UserData {
// value = "姓名" 对应 Excel 中的表头
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
@ExcelProperty("邮箱")
private String email;
}
编写导出服务
创建一个服务类,调用 EasyExcel 的 write 方法来写入数据。
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ExcelExportService {
public void export(HttpServletResponse response) throws IOException {
// 1. 设置响应头
String fileName = "用户数据_" + System.currentTimeMillis() + ".xlsx";
// URLEncoder.encode 用于处理文件名中的中文和空格
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
// 2. 准备模拟数据
List<UserData> dataList = new ArrayList<>();
for (int i = 1; i <= 10000; i++) {
UserData data = new UserData();
data.setName("用户" + i);
data.setAge(20 + (i % 30));
data.setEmail("user" + i + "@example.com");
dataList.add(data);
}
// 3. 使用 try-with-resources 确保 ExcelWriter 自动关闭
try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), UserData.class).build()) {
// 创建一个 sheet,名称为 "用户信息"
WriteSheet writeSheet = EasyExcel.writerSheet("用户信息").build();
// 将数据写入 sheet
excelWriter.write(dataList, writeSheet);
}
}
}
代码解析:

(图片来源网络,侵删)
HttpServletResponse: 在 Web 应用中,通常通过这个对象将文件流写入到 HTTP 响应中,供浏览器下载。- 响应头设置: 这是实现文件下载的关键。
Content-Type: 告诉浏览器这是一个 Excel 文件。Content-disposition:attachment表示附件,触发下载;filename指定下载时的文件名。
EasyExcel.write(...): 开始构建一个写入操作。response.getOutputStream(): 获取输出流。UserData.class: 指定写入的数据模型。
excelWriter.write(...): 执行写入操作,将数据列表写入到指定的 Sheet 中。
使用 Apache POI
Apache POI 是 Java 操作 Office 文件最老牌、最强大的库,它功能全面,但 API 相对繁琐,且大数据量下内存消耗大。
添加 Maven 依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version> <!-- 请使用最新版本 -->
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version> <!-- 用于支持 .xlsx 格式 -->
</dependency>
准备数据模型
POI 不需要特殊的注解,我们直接使用普通的 Java 类。
public class UserData {
private String name;
private Integer age;
private String email;
// 必须要有无参构造器
public UserData() {}
public UserData(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
编写导出服务
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class PoiExcelExportService {
public void export(HttpServletResponse response) throws IOException {
// 1. 设置响应头 (与 EasyExcel 相同)
String fileName = "用户数据_POI_" + System.currentTimeMillis() + ".xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
// 2. 创建 Excel 工作簿
try (Workbook workbook = new XSSFWorkbook()) {
// 3. 创建工作表
Sheet sheet = workbook.createSheet("用户信息");
// 4. 创建表头行
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("姓名");
headerRow.createCell(1).setCellValue("年龄");
headerRow.createCell(2).setCellValue("邮箱");
// 5. 准备模拟数据
List<UserData> dataList = new ArrayList<>();
for (int i = 1; i <= 10000; i++) {
dataList.add(new UserData("用户" + i, 20 + (i % 30), "user" + i + "@example.com"));
}
// 6. 写入数据
int rowNum = 1;
for (UserData data : dataList) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(data.getName());
row.createCell(1).setCellValue(data.getAge());
row.createCell(2).setCellValue(data.getEmail());
}
// 7. 自动调整列宽(可选)
for (int i = 0; i < 3; i++) {
sheet.autoSizeColumn(i);
}
// 8. 将工作簿写入输出流
workbook.write(response.getOutputStream());
}
}
}
代码解析:
Workbook: 代表整个 Excel 文档。XSSFWorkbook用于.xlsx格式,HSSFWorkbook用于旧的.xls格式。Sheet: 代表 Excel 中的一个工作表。Row: 代表一行。Cell: 代表一个单元格。- POI 的 API 更偏向于“手动操作”,需要你手动创建行、创建单元格并设置值,代码量比 EasyExcel 多。
高级功能:复杂样式和下拉列表
以 EasyExcel 为例,展示如何添加复杂样式和下拉列表。
创建样式工具类
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
public class ExcelStyleUtil {
public static WriteHeaderCellStyle getHeadStyle() {
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景色
headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
// 字体
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 11);
headWriteFont.setBold(true);
headWriteCellStyle.setWriteFont(headWriteFont);
// 居中
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
return headWriteCellStyle;
}
public static WriteCellStyle getContentStyle() {
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 字体
WriteFont contentWriteFont = new WriteFont();
contentWriteFont.setFontHeightInPoints((short) 10);
contentWriteCellStyle.setWriteFont(contentWriteFont);
// 垂直居中
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 水平居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
return contentWriteCellStyle;
}
}
在导出服务中使用样式和下拉列表
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.handler.AbstractCellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.ArrayList;
import java.util.List;
public class AdvancedExcelExportService {
public void exportWithStylesAndDropdown(HttpServletResponse response) throws Exception {
// ... (响应头设置与前面相同) ...
String fileName = "高级Excel_" + System.currentTimeMillis() + ".xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
// 1. 准备数据
List<AdvancedUserData> dataList = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
dataList.add(new AdvancedUserData("产品" + i, "是", "正常"));
}
// 2. 创建下拉列表数据
String[] dropdownList1 = {"是", "否"};
String[] dropdownList2 = {"正常", "异常", "待处理"};
try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), AdvancedUserData.class)
.registerWriteHandler(new AbstractCellWriteHandler() {
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
// 在表头创建后执行
if (isHead) {
Sheet sheet = writeSheetHolder.getSheet();
// 为 "是否有效" 列 (第2列) 设置下拉列表
addDropdownValidation(sheet, 1, 1, dropdownList1);
// 为 "状态" 列 (第3列) 设置下拉列表
addDropdownValidation(sheet, 2, 2, dropdownList2);
}
}
})
.registerWriteHandler(ExcelStyleUtil.getHeadStyle()) // 注册表头样式
.registerWriteHandler(new CustomCellWriteHandler()) // 注册内容样式
.build()) {
WriteSheet writeSheet = EasyExcel.writerSheet("高级数据").build();
excelWriter.write(dataList, writeSheet);
}
}
/**
* 添加下拉列表验证
* @param sheet 工作表
* @param firstCol 起始列
* @param lastCol 结束列
* @param list 下拉列表内容
*/
private void addDropdownValidation(Sheet sheet, int firstCol, int lastCol, String[] list) {
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = helper.createExplicitListConstraint(list);
CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, firstCol, lastCol); // 从第二行到最后一行
DataValidation validation = helper.createValidation(constraint, addressList);
// 防止输入下拉列表以外的值
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.createErrorPrompt("错误", "请从下拉列表中选择有效的值!");
sheet.addValidationData(validation);
}
}
// 自定义内容样式处理器
class CustomCellWriteHandler extends AbstractCellWriteHandler {
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
if (!isHead) {
WriteCellStyle contentStyle = ExcelStyleUtil.getContentStyle();
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
CellStyle cellStyle = contentStyle.buildCellStyle(workbook);
cell.setCellStyle(cellStyle);
}
}
}
// 高级数据模型
class AdvancedUserData {
@ExcelProperty("产品名称")
private String productName;
@ExcelProperty("是否有效")
private String isValid;
@ExcelProperty("状态")
private String status;
// 构造器、getter/setter...
}
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 常规数据导出 | EasyExcel | 代码简洁,性能好,内存占用低 |
| 大数据量导出 (万行+) | EasyExcel | 基于 SAX 模式,不会 OOM |
| 需要复杂样式、图表、公式 | Apache POI | 功能最全面,API 底层但强大 |
| 旧项目维护 (JXL) | JXL | 如果不想引入新依赖,可以继续使用,但新项目请勿选用 |
对于绝大多数现代 Java 项目,EasyExcel 是毫无疑问的最佳选择,它既简单易用,又能满足高性能的需求,只有在遇到 EasyExcel 无法解决的极端复杂场景时,才需要考虑使用 Apache POI。
