什么是 Apache POI?
Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,由 Apache 软件基金会维护,它允许程序员使用 Java 代码来操作 Microsoft Office 格式的文件,包括:

- Excel (
.xls,.xlsx) - Word (
.doc,.docx) - PowerPoint (
.ppt,.pptx)
对于我们生成 Excel 的需求,主要关注 POI 的 HSSF 和 XSSF 模块。
POI 的核心模块
在开始之前,你需要理解 POI 针对 Excel 的不同版本有两个核心模块:
| 模块 | 扩展名 | 描述 | 优点 | 缺点 |
|---|---|---|---|---|
| HSSF | .xls |
操作 Excel 97-2003 格式。 | 兼容性好,几乎所有 Excel 版本都能打开。 | 单个工作簿最多支持 65536 行、256 列,文件格式老旧。 |
| XSSF | .xlsx |
操作 Excel 2007 及以上格式 (OOXML)。 | 支持超大文件(行数和列数限制极大),功能更丰富。 | 需要较高版本的 Java 支持,旧版 Excel 可能无法直接打开。 |
| SXSSF | .xlsx |
XSSF 的大数据量流式 API。 | 内存效率极高,适用于生成数百万行的超大文件。 | 不支持随机访问,已写入的数据不能修改或删除。 |
对于新项目,强烈推荐使用 XSSF 或 SXSSF。
环境准备 (Maven 依赖)
你需要在你的项目中添加 POI 的依赖,这里以 Maven 为例。

1. 生成 .xlsx 文件 (推荐)
这是目前最常用的方式。
<dependencies>
<!-- POI 核心依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version> <!-- 请使用最新稳定版 -->
</dependency>
<!-- 针对 .xlsx 格式的依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- 为了支持一些高级特性,可能需要 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
2. 生成 .xls 文件 (旧版)
如果你的用户还在使用非常古老的 Excel,你可能需要这个。
<dependencies>
<!-- 仅需 poi 即可 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
核心概念和 API
在写代码之前,了解几个核心类非常重要:
Workbook: 工作簿,代表一个 Excel 文件,对于.xlsx,它是XSSFWorkbook;对于.xls,它是HSSFWorkbook。Sheet: 工作表,一个Workbook可以包含多个Sheet(如 "Sheet1", "Sheet2")。Row: 行,一个Sheet由多行组成。Cell: 单元格,一个Row由多个单元格组成。CellStyle: 单元格样式,可以设置字体、颜色、边框、对齐方式等。Font: 字体,用于设置CellStyle中的字体样式。
完整代码示例 (生成 .xlsx 文件)
下面是一个完整的、可运行的 Java 示例,它会创建一个名为 students.xlsx 的文件,并包含一些格式化的数据。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
public class PoiExcelExample {
public static void main(String[] args) {
// 1. 创建一个新的 Workbook (XSSFWorkbook for .xlsx)
Workbook workbook = new XSSFWorkbook();
// 2. 创建一个 Sheet,并命名
Sheet sheet = workbook.createSheet("学生信息");
// 3. 创建标题行 (Row 0)
Row headerRow = sheet.createRow(0);
// 4. 创建标题单元格并设置值
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("入学日期");
headerRow.createCell(4).setCellValue("成绩");
// 5. 创建一些样式 (可选)
// 5.1 创建一个用于标题的样式
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true); // 加粗
headerFont.setFontHeightInPoints((short) 12);
headerStyle.setFont(headerFont);
headerStyle.setAlignment(HorizontalAlignment.CENTER); // 居中对齐
// 5.2 将样式应用到标题行
for (Cell cell : headerRow) {
cell.setCellStyle(headerStyle);
}
// 6. 创建数据行
Object[][] studentData = {
{1, "张三", 20, new Date(), 95.5},
{2, "李四", 21, new Date(), 88.0},
{3, "王五", 19, new Date(), 92.5},
{4, "赵六", 22, new Date(), 76.5}
};
for (int i = 0; i < studentData.length; i++) {
Row row = sheet.createRow(i + 1); // 数据从第二行开始
Object[] rowData = studentData[i];
row.createCell(0).setCellValue((Integer) rowData[0]);
row.createCell(1).setCellValue((String) rowData[1]);
row.createCell(2).setCellValue((Integer) rowData[2]);
// 日期格式化
Cell dateCell = row.createCell(3);
dateCell.setCellValue((Date) rowData[3]);
CellStyle dateStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
dateCell.setCellStyle(dateStyle);
row.createCell(4).setCellValue((Double) rowData[4]);
}
// 7. 自动调整列宽,使内容能够完整显示
for (int i = 0; i < headerRow.getPhysicalNumberOfCells(); i++) {
sheet.autoSizeColumn(i);
}
// 8. 将 Workbook 写入文件
try (FileOutputStream fileOut = new FileOutputStream("students.xlsx")) {
workbook.write(fileOut);
System.out.println("Excel 文件 'students.xlsx' 生成成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 9. 关闭 Workbook,释放资源
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
你会得到一个名为 students.xlsx 的文件,内容如下:
| ID | 姓名 | 年龄 | 入学日期 | 成绩 |
|---|---|---|---|---|
| 1 | 张三 | 20 | 2025-10-27 | 5 |
| 2 | 李四 | 21 | 2025-10-27 | 0 |
| 3 | 王五 | 19 | 2025-10-27 | 5 |
| 4 | 赵六 | 22 | 2025-10-27 | 5 |
常用操作总结
1. 设置单元格样式
// 1. 创建 CellStyle
CellStyle style = workbook.createCellStyle();
// 2. 设置背景色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 3. 设置边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 4. 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 5. 设置字体
Font font = workbook.createFont();
font.setBold(true);
font.setColor(IndexedColors.BLUE.getIndex());
style.setFont(font);
// 6. 应用样式到单元格
Cell cell = row.createCell(0);
cell.setCellValue("带样式的单元格");
cell.setCellStyle(style);
2. 合并单元格
// 参数:起始行, 结束行, 起始列, 结束列
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2)); // 合并第一行的前三列
// 在合并区域的第一个单元格设置值,其他单元格为空
Cell mergedCell = sheet.getRow(0).getCell(0);
mergedCell.setCellValue("合并的标题");
3. 设置下拉列表
// 1. 创建一个 Sheet 来存放下拉列表的选项 (隐藏这个 Sheet)
Sheet hiddenSheet = workbook.createSheet("DropDownOptions");
// 隐藏 Sheet
workbook.setSheetHidden(workbook.getSheetIndex("DropDownOptions"), true);
// 2. 在隐藏的 Sheet 中添加选项
for (int i = 0; i < options.length; i++) {
hiddenSheet.createRow(i).createCell(0).setCellValue(options[i]);
}
// 3. 创建名称,指向这些选项
Name namedCell = workbook.createName();
namedCell.setNameName("dropdownList"); // 给这个命名范围起个名字
namedCell.setRefersToFormula("DropDownOptions!$A$1:$A$" + options.length);
// 4. 设置数据验证 (在目标单元格上)
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = helper.createFormulaListConstraint("dropdownList");
CellRangeAddressList addressList = new CellRangeAddressList(1, 10, 0, 0); // 应用于第2到第11行的第1列
DataValidation validation = helper.createValidation(constraint, addressList);
sheet.addValidationData(validation);
大数据量处理 (SXSSF)
当需要生成超过 10 万行的 Excel 时,使用 XSSFWorkbook 会导致内存溢出(OOM),这时必须使用 SXSSF (Streaming Usermodel API)。
核心思想:SXSSF 会将一部分数据(默认100行)保存在内存中,其余数据会临时写入磁盘的临时文件,当数据行超过这个阈值时,最早的数据会被写入最终文件并从内存中清除。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class SxssfExample {
public static void main(String[] args) throws IOException {
// 1. 创建 SXSSFWorkbook,参数 100 表示在内存中保留的行数
Workbook workbook = new SXSSFWorkbook(100);
Sheet sheet = workbook.createSheet("大数据量");
// 2. 创建标题行
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("行号");
// 3. 模拟写入 1,000,000 行数据
for (int i = 1; i <= 1_000_000; i++) {
Row row = sheet.createRow(i);
row.createCell(0).setCellValue(i);
// 每 1000 行刷新一次进度
if (i % 1000 == 0) {
System.out.println("已写入 " + i + " 行...");
}
}
// 4. 写入文件
try (FileOutputStream out = new FileOutputStream("large_data.xlsx")) {
workbook.write(out);
System.out.println("大数据量 Excel 文件生成完成!");
} finally {
// 5. 关闭 Workbook,非常重要!这会清理临时文件
((SXSSFWorkbook) workbook).dispose();
workbook.close();
}
}
}
注意:SXSSFWorkbook 的限制:
- 不能随机访问:一旦数据被写入文件,就不能再修改或读取它。
- 不能使用
Sheet.autoSizeColumn():因为它需要访问所有数据来计算宽度,这在流式API中是不可能的,你需要手动设置列宽。
最佳实践
-
选择正确的 API:
- 小文件 (< 100,000 行): 使用
XSSFWorkbook。 - 大文件 (> 100,000 行): 必须使用
SXSSFWorkbook。 - 需要兼容旧版 Excel: 使用
HSSFWorkbook。
- 小文件 (< 100,000 行): 使用
-
资源管理:始终使用
try-with-resources语句来包裹FileOutputStream和Workbook,确保文件流和资源被正确关闭,防止内存泄漏和文件锁定。 -
样式复用:不要为每个单元格都创建一个新的
CellStyle,如果多个单元格需要相同的样式,创建一个CellStyle对象,然后重复应用到多个单元格上。 -
性能优化:对于
SXSSF,合理设置windowSize(内存中保留的行数),值越大,性能越好,但内存消耗也越大。 -
日期处理:使用
CreationHelper来创建日期格式,并应用到包含日期的CellStyle上,而不是直接在setCellValue时格式化字符串。
希望这份详细的指南能帮助你掌握使用 Java POI 生成 Excel 的技能!
