Apache POI 是一个强大的 Java API,它允许你创建、修改和显示 MS Office 格式的文件,包括 .xls (Excel 97-2003) 和 .xlsx (Excel 2007+) 格式。

目录
- 准备工作:添加 POI 依赖
- 核心概念:
XSSFWorkbookvsHSSFWorkbook - 基本操作(以
.xlsx为例)- 1 创建新的 Excel 文件并写入数据
- 2 读取已有的 Excel 文件
- 3 修改已有的 Excel 文件
- 常用功能详解
- 1 样式设置(字体、颜色、边框)
- 2 合并单元格
- 3 调整列宽
- 4 处理日期
- 5 下拉列表
- 性能优化:
SXSSFWorkbook - 完整示例代码
- 总结与注意事项
准备工作:添加 POI 依赖
你需要在你的项目中添加 Apache POI 的依赖,如果你使用 Maven,请在 pom.xml 文件中添加以下依赖:
<!-- 针对 .xlsx (OOXML) 格式的依赖 -->
<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>
</dependency>
<!-- 针对 .xls (BIFF) 格式的依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<!-- 如果需要处理公式,可能需要这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
如果你使用 Gradle,在 build.gradle 文件中添加:
implementation 'org.apache.poi:poi:5.2.5' implementation 'org.apache.poi:poi-ooxml:5.2.5'
核心概念:XSSFWorkbook vs HSSFWorkbook
这是使用 POI 时最重要的区分点,直接决定了你能操作的文件格式。
| 特性 | HSSFWorkbook |
XSSFWorkbook |
|---|---|---|
| 文件格式 | .xls (Excel 97-2003) |
.xlsx (Excel 2007 及以上) |
| 底层结构 | 基于 BIFF 格式,所有数据都加载到内存中 | 基于 OOXML (Open Office XML) 格式,结构更复杂 |
| 内存占用 | 较低,但处理大文件时会非常消耗内存 | 较高,但提供了流式 API (SXSSF) 来优化大文件 |
| POI 模块 | poi |
poi-ooxml |
选择建议:

- 新项目:强烈推荐使用
.xlsx格式,因为它功能更强大、文件更小、结构更标准,使用XSSFWorkbook。 - 旧系统兼容:如果必须与旧版 Excel (2003及以前) 兼容,则使用
.xls格式和HSSFWorkbook。
基本操作(以 .xlsx 为例)
1 创建新的 Excel 文件并写入数据
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class CreateExcel {
public static void main(String[] args) {
// 1. 创建一个新的 XSSFWorkbook 对象,代表一个 .xlsx 文件
Workbook workbook = new XSSFWorkbook();
try {
// 2. 创建一个工作表,名为 "Sheet1"
Sheet sheet = workbook.createSheet("员工信息");
// 3. 创建标题行 (Row 0)
Row headerRow = sheet.createRow(0);
// 创建单元格并设置值
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("入职日期");
// 4. 创建数据行
Row dataRow1 = sheet.createRow(1);
dataRow1.createCell(0).setCellValue(1);
dataRow1.createCell(1).setCellValue("张三");
dataRow1.createCell(2).setCellValue(28);
dataRow1.createCell(3).setCellValue(setDateCellValue(workbook, "2025-05-15")); // 设置日期
Row dataRow2 = sheet.createRow(2);
dataRow2.createCell(0).setCellValue(2);
dataRow2.createCell(1).setCellValue("李四");
dataRow2.createCell(2).setCellValue(32);
dataRow2.createCell(3).setCellValue(setDateCellValue(workbook, "2025-08-20"));
// 5. 调整列宽以适应内容
sheet.autoSizeColumn(0);
sheet.autoSizeColumn(1);
sheet.autoSizeColumn(2);
sheet.autoSizeColumn(3);
// 6. 将 workbook 写入文件输出流
try (FileOutputStream fileOut = new FileOutputStream("D:/temp/员工信息.xlsx")) {
workbook.write(fileOut);
System.out.println("Excel 文件创建成功!");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 7. 关闭 workbook,释放资源
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 辅助方法:创建日期格式的单元格
private static Cell setDateCellValue(Workbook workbook, String dateString) {
CreationHelper createHelper = workbook.getCreationHelper();
Cell cell = null; // 假设这个方法在某个 Row 的 createCell 后调用
cell.setCellValue(createHelper.createFormula("DATEVALUE(\"" + dateString + "\")"));
// 或者直接使用 Date 对象
// cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").parse(dateString));
// 应用日期格式
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
cell.setCellStyle(dateStyle);
return cell;
}
}
2 读取已有的 Excel 文件
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcel {
public static void main(String[] args) {
String filePath = "D:/temp/员工信息.xlsx";
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) {
// 1. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 2. 遍历所有行
for (Row row : sheet) {
// 3. 遍历当前行的所有单元格
for (Cell cell : row) {
// 根据单元格类型获取值
switch (cell.getCellType()) {
case STRING:
System.out.print(cell.getStringCellValue() + "\t");
break;
case NUMERIC:
// 检查是否是日期格式
if (DateUtil.isCellDateFormatted(cell)) {
System.out.print(cell.getDateCellValue() + "\t");
} else {
System.out.print(cell.getNumericCellValue() + "\t");
}
break;
case BOOLEAN:
System.out.print(cell.getBooleanCellValue() + "\t");
break;
case FORMULA:
System.out.print(cell.getCellFormula() + "\t");
break;
default:
System.out.print("\t");
}
}
System.out.println(); // 换行
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3 修改已有的 Excel 文件
注意:修改文件的本质是“读取 -> 修改 -> 写入新文件”,直接在原文件上修改通常是不安全的,容易导致文件损坏。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ModifyExcel {
public static void main(String[] args) {
String filePath = "D:/temp/员工信息.xlsx";
String tempPath = "D:/temp/员工信息_temp.xlsx";
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis);
FileOutputStream fos = new FileOutputStream(tempPath)) {
// 1. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 2. 找到要修改的行(ID为2的行)
for (Row row : sheet) {
if (row.getCell(0).getNumericCellValue() == 2.0) {
// 3. 修改单元格内容
Cell nameCell = row.getCell(1);
nameCell.setCellValue("李四 (已更新)");
// 4. 也可以修改样式
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setColor(IndexedColors.RED.getIndex());
style.setFont(font);
nameCell.setCellStyle(style);
break; // 找到后退出循环
}
}
// 5. 将修改后的内容写入新文件
workbook.write(fos);
System.out.println("Excel 文件修改成功,已保存为: " + tempPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
常用功能详解
1 样式设置
样式通过 CellStyle 和 Font 对象来创建和设置。
// 在 Workbook 中创建样式
CellStyle style = workbook.createCellStyle();
// 设置背景色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 设置字体
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 12);
font.setBold(true);
style.setFont(font);
// 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 将样式应用到单元格
cell.setCellStyle(style);
2 合并单元格
使用 Sheet 的 addMergedRegion 方法。
// 合并从 (row1, col1) 到 (row2, col2) 的区域
// 参数是 CellRangeAddress 对象
CellRangeAddress range = new CellRangeAddress(0, 0, 0, 3); // 合并第1行的第1列到第4列
sheet.addMergedRegion(range);
// 在合并后的区域的第一个单元格写入数据
sheet.getRow(0).getCell(0).setCellValue("合并单元格标题");
3 调整列宽
// 自动调整列宽 sheet.autoSizeColumn(0); // 调整第一列 sheet.autoSizeColumn(1); // 调整第二列 // 手动设置列宽(单位是 1/256 个字符的宽度) sheet.setColumnWidth(0, 20 * 256); // 设置第一列宽度为20个字符
4 处理日期
直接设置 Date 对象到单元格是不够的,必须配合 CellStyle 来指定显示格式。

// 1. 创建单元格
Cell cell = row.createCell(3);
// 2. 创建日期样式
CellStyle dateStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy年mm月dd日 hh:mm:ss"));
// 3. 创建日期对象并设置到单元格
Date date = new Date();
cell.setCellValue(date);
// 4. 应用样式
cell.setCellStyle(dateStyle);
5 下拉列表
通过数据有效性(Data Validation)实现。
// 1. 创建一个隐藏的 sheet 来存放下拉列表的选项值
Sheet hiddenSheet = workbook.createSheet("hiddenOptions");
Row hiddenRow = hiddenSheet.createRow(0);
hiddenRow.createCell(0).setCellValue("选项A");
hiddenRow.createCell(1).setCellValue("选项B");
hiddenRow.createCell(2).setCellValue("选项C");
// 2. 定义名称,指向选项区域
Name namedCell = workbook.createName();
namedCell.setNameName("dropdownList");
namedCell.setRefersToFormula("hiddenOptions!$A$1:$C$1");
// 3. 在目标 sheet 的目标单元格上设置数据有效性
Sheet mainSheet = workbook.getSheet("主数据");
CellAddress address = new CellAddress(1, 1); // 第2行,第2列
DataValidationHelper validationHelper = mainSheet.getDataValidationHelper();
DataValidationConstraint constraint = validationHelper.createFormulaListConstraint("dropdownList");
DataValidation validation = validationHelper.createValidation(constraint, new CellRangeAddressList(1, 1, 1, 1)); // 只对 B2 单元格有效
// 防止输入下拉列表中没有的值
validation.setShowErrorBox(true);
mainSheet.addValidationData(validation);
性能优化:SXSSFWorkbook
当处理非常大的 Excel 文件(几十万行数据)时,XSSFWorkbook 会将所有数据都加载到内存中,很容易导致 OutOfMemoryError。
SXSSFWorkbook (Streaming Usermodel API) 是为此而生的解决方案,它基于 XSSFWorkbook,但采用临时文件和滑动窗口的机制,只在内存中保留一部分行,其余的行会写入临时磁盘文件,从而极大地减少内存消耗。
核心思想:SXSSFWorkbook 会创建一个 .xlsx 文件,但它在内存中只保留 windowSize 数量的行,当写入的行数超过这个窗口时,最旧的行会被“刷新”到磁盘上。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import java.io.FileOutputStream;
import java.io.IOException;
public class BigExcelExample {
public static void main(String[] args) throws IOException {
// 1. 创建 SXSSFWorkbook,设置窗口大小为 100 (内存中保留100行)
Workbook workbook = new SXSSFWorkbook(100);
try {
Sheet sheet = workbook.createSheet("大数据量");
// 2. 写入大量数据 (10 万行)
for (int i = 0; i < 100000; i++) {
Row row = sheet.createRow(i);
row.createCell(0).setCellValue("ID " + i);
row.createCell(1).setCellValue("数据 " + i);
// 每1000行打印一次进度
if (i % 1000 == 0) {
System.out.println("已写入 " + i + " 行...");
}
}
// 3. 写入文件
try (FileOutputStream out = new FileOutputStream("D:/temp/大数据量.xlsx")) {
workbook.write(out);
System.out.println("大数据量 Excel 文件创建成功!");
}
} finally {
// 4. 清理临时文件!非常重要!
((SXSSFWorkbook) workbook).dispose();
}
}
}
注意:
SXSSFWorkbook不支持随机访问和修改,它只能按顺序写入。- 必须调用
dispose()方法来删除在磁盘上生成的临时文件。
完整示例代码
这里提供一个结合了创建、样式、合并、日期和自动列宽的完整示例。
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 CompleteExample {
public static void main(String[] args) {
Workbook workbook = new XSSFWorkbook();
try {
Sheet sheet = workbook.createSheet("综合示例");
// 1. 创建带样式的标题
Row titleRow = sheet.createRow(0);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellValue("综合示例报表");
// 创建标题样式
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true);
titleStyle.setFont(titleFont);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleCell.setCellStyle(titleStyle);
// 2. 合并标题单元格
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
// 3. 创建表头
Row headerRow = sheet.createRow(2);
String[] headers = {"产品名称", "销售数量", "单价", "销售日期", "备注"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
// 应用表头样式 (可以单独创建)
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
cell.setCellStyle(headerStyle);
}
// 4. 创建数据行
Object[][] data = {
{"笔记本电脑", 120, 5999.99, "2025-10-01", "热销款"},
{"无线鼠标", 350, 99.50, "2025-10-02", null},
{"机械键盘", 80, 399.00, "2025-10-03", "青轴"}
};
for (int i = 0; i < data.length; i++) {
Row row = sheet.createRow(3 + i);
for (int j = 0; j < data[i].length; j++) {
Object value = data[i][j];
Cell cell = row.createCell(j);
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Double) {
cell.setCellValue((Double) value);
} else if (value instanceof Date) {
cell.setCellValue((Date) value);
}
}
}
// 5. 设置日期格式
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
sheet.getRow(4).getCell(3).setCellStyle(dateStyle); // 应用到第一个日期单元格
// 6. 自动调整列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 7. 写入文件
try (FileOutputStream fos = new FileOutputStream("D:/temp/综合示例.xlsx")) {
workbook.write(fos);
System.out.println("综合示例 Excel 文件创建成功!");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
总结与注意事项
- 依赖选择:明确你的目标文件格式是
.xls还是.xlsx,选择对应的HSSFWorkbook或XSSFWorkbook。 - 资源管理:
Workbook和所有InputStream/OutputStream都应该在try-finally或try-with-resources块中关闭,以防止资源泄漏。 - 日期处理:设置日期值时,务必配合
CellStyle来指定显示格式,否则可能显示为数字。 - 大文件处理:对于大数据量,请务必使用
SXSSFWorkbook,并记得在最后调用dispose()。 - 性能:频繁的样式创建会消耗性能,如果多个单元格需要相同的样式,应该创建一个
CellStyle对象并重复使用,而不是为每个单元格都新建一个。 - 异常处理:文件操作(读写)可能会抛出
IOException,需要进行妥善处理。
