Apache POI 是一个强大的 Java 库,专门用于操作 Microsoft Office 格式的文件,包括 Excel(.xls 和 .xlsx)。

目录
- 准备工作:添加 POI 依赖
- 核心概念:
XSSFWorkbookvsHSSFWorkbook - 写入 Excel 文件
- 写入
.xlsx(Excel 2007+) - 写入
.xls(Excel 97-2003) - 设置单元格样式(字体、颜色、边框等)
- 调整列宽
- 合并单元格
- 写入
- 读取 Excel 文件
- 读取
.xlsx(Excel 2007+) - 读取
.xls(Excel 97-2003) - 获取单元格数据
- 处理不同类型的数据(字符串、数字、日期等)
- 读取
- 完整示例代码
- 最佳实践与注意事项
准备工作:添加 POI 依赖
您需要在您的项目中添加 Apache POI 的依赖,如果您使用 Maven,请在 pom.xml 文件中添加以下依赖:
<!-- 对于较新版本的 POI,建议使用这个方式 -->
<dependencies>
<!-- 核心 POI 库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version> <!-- 请使用最新稳定版 -->
</dependency>
<!-- 用于处理 .xlsx 格式 (OOXML) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- 用于处理 XML,是 poi-ooxml 的依赖 -->
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>5.1.1</version>
</dependency>
<!-- 用于处理 .xlsx 中的图片等 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
注意:.xlsx 格式是现代 Excel 的标准,推荐优先使用,它需要 poi 和 poi-ooxml 两个依赖,而 .xls 格式只需要 poi 依赖即可。
核心概念:XSSFWorkbook vs HSSFWorkbook
这是使用 POI 时最重要的区别:
| 特性 | HSSFWorkbook |
XSSFWorkbook |
|---|---|---|
| 文件格式 | .xls (BIFF 格式) |
.xlsx (OOXML 格式) |
| Excel 版本 | Excel 97-2003 | Excel 2007 及更高版本 |
| 扩展性 | 最大支持 65536 行,256 列 | 最大支持 1,048,576 行,16,384 列 |
| 内存占用 | 相对较低 | 相对较高,但 API 更现代 |
| API 设计 | 旧版 API | 新版 API,与旧版不兼容 |
简单来说:

- 如果你要处理新版本的 Excel 文件(
.xlsx),使用XSSFWorkbook。 - 如果你要处理旧版本的 Excel 文件(
.xls),使用HSSFWorkbook。
写入 Excel 文件
1 写入 .xlsx (Excel 2007+)
这是最推荐的方式。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class WriteExcelXLSX {
public static void main(String[] args) {
// 1. 创建一个新的 Workbook 对象 (代表整个 Excel 文件)
Workbook workbook = new XSSFWorkbook();
try {
// 2. 创建一个 Sheet (工作表)
Sheet sheet = workbook.createSheet("员工信息");
// 3. 创建 Row (行) 和 Cell (单元格)
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("年龄");
headerRow.createCell(3).setCellValue("入职日期");
// 创建数据行
Row dataRow1 = sheet.createRow(1);
dataRow1.createCell(0).setCellValue(1);
dataRow1.createCell(1).setCellValue("张三");
dataRow1.createCell(2).setCellValue(30);
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(28);
dataRow2.createCell(3).setCellValue(setDateCellValue(workbook, "2025-08-20"));
// 4. 调整列宽以适应内容
sheet.autoSizeColumn(0);
sheet.autoSizeColumn(1);
sheet.autoSizeColumn(2);
sheet.autoSizeColumn(3);
// 5. 将 Workbook 写入到文件输出流
try (FileOutputStream fileOut = new FileOutputStream("employees.xlsx")) {
workbook.write(fileOut);
System.out.println("employees.xlsx 文件已成功创建!");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 6. 关闭 Workbook,释放资源
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 创建一个日期格式的单元格
*/
private static Cell setDateCellValue(Workbook workbook, String dateStr) {
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
Cell cell = null; // 假设这个 cell 已经被创建
cell.setCellStyle(dateStyle);
// 注意:这里需要单独设置 cell 的值,因为 setCellStyle 不会设置值
// 实际使用时,应该先创建 cell,再设置样式,再设置值
// 为简化示例,这里返回一个已设置好样式的 Cell 对象供外部设置值
return cell;
}
}
2 设置单元格样式(字体、颜色、边框)
// ... (在写入文件的代码中) CellStyle style = workbook.createCellStyle(); Font font = workbook.createFont(); // 设置字体为粗体 font.setBold(true); style.setFont(font); // 设置背景色 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); // 应用样式到单元格 headerRow.getCell(0).setCellStyle(style);
3 调整列宽和合并单元格
// ... (在写入文件的代码中) // 调整第一列的宽度为 15 个字符宽度 sheet.setColumnWidth(0, 15 * 256); // 宽度单位是 1/256 个字符宽度 // 合并单元格 (参数:起始行, 结束行, 起始列, 结束列) sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3)); // 合并第一行的第0列到第3列
读取 Excel 文件
读取文件的流程与写入相反:先打开文件流,创建 Workbook,然后循环遍历 Sheet、Row 和 Cell。
1 读取 .xlsx (Excel 2007+)
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelXLSX {
public static void main(String[] args) {
String filePath = "employees.xlsx";
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) {
// 1. 获取第一个 Sheet
Sheet sheet = workbook.getSheetAt(0); // 或 getSheet("员工信息")
// 2. 遍历每一行 (从0开始,0是表头)
for (Row row : sheet) {
// 跳过表头行
if (row.getRowNum() == 0) {
continue;
}
// 3. 遍历每一个单元格
for (Cell cell : row) {
// 4. 根据单元格类型获取值
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;
case BLANK:
System.out.print("[空]\t");
break;
default:
System.out.print("[未知类型]\t");
}
}
System.out.println(); // 换行
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2 读取 .xls (Excel 97-2003)
代码几乎完全一样,只需要将 XSSFWorkbook 替换为 HSSFWorkbook,文件流改为 .xls 文件即可。
// ...
try (FileInputStream fis = new FileInputStream("employees.xls");
Workbook workbook = new HSSFWorkbook(fis)) {
// ... 其余代码完全相同
}
// ...
完整示例代码
下面是一个结合了读取和写入的完整示例,它从一个模板 .xlsx 文件读取数据,处理后写入一个新的文件。

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 PoiReadWriteExample {
public static void main(String[] args) {
String inputFile = "template.xlsx"; // 假设这个文件存在
String outputFile = "output.xlsx";
try (FileInputStream fis = new FileInputStream(inputFile);
Workbook workbook = new XSSFWorkbook(fis);
FileOutputStream fos = new FileOutputStream(outputFile)) {
// --- 1. 读取操作 ---
Sheet readSheet = workbook.getSheet("数据源");
System.out.println("--- 读取模板文件 ---");
for (Row row : readSheet) {
for (Cell cell : row) {
System.out.print(getCellValueAsString(cell) + "\t");
}
System.out.println();
}
// --- 2. 写入操作 ---
Sheet writeSheet = workbook.createSheet("处理结果");
// 创建标题行
Row titleRow = writeSheet.createRow(0);
titleRow.createCell(0).setCellValue("处理后的ID");
titleRow.createCell(1).setCellValue("处理后的名称");
// 模拟数据处理
int id = 100;
String name = "新用户";
Row dataRow = writeSheet.createRow(1);
dataRow.createCell(0).setCellValue(id);
dataRow.createCell(1).setCellValue(name);
// 设置样式
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
titleRow.getCell(0).setCellStyle(headerStyle);
titleRow.getCell(1).setCellStyle(headerStyle);
writeSheet.autoSizeColumn(0);
writeSheet.autoSizeColumn(1);
// 3. 写入文件
workbook.write(fos);
System.out.println("\n--- 数据已成功写入 " + outputFile + " ---");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将单元格值转换为字符串
*/
private static String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
return String.valueOf(cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
return cell.getCellFormula();
case BLANK:
return "";
default:
return "";
}
}
}
最佳实践与注意事项
-
使用
try-with-resources:Workbook,FileInputStream,FileOutputStream等都实现了AutoCloseable接口,使用try-with-resources语句可以确保它们在使用后被自动关闭,避免资源泄漏。try (Workbook workbook = new XSSFWorkbook()) { // ... 操作 workbook } // workbook 在这里会自动关闭 -
选择正确的 Workbook 类型:始终根据文件格式(
.xls或.xlsx)选择HSSFWorkbook或XSSFWorkbook,对于新项目,强烈推荐只使用.xlsx和XSSFWorkbook。 -
处理大数据量(
SXSSF):如果需要处理非常大的 Excel 文件(包含数万行数据),使用XSSFWorkbook会导致内存溢出,这时应该使用SXSSFWorkbook(流 API)。SXSSFWorkbook是XSSFWorkbook的一个流式实现,它只在内存中保留一定数量的行(默认100行),其余的行会临时写入磁盘。- 示例:
Workbook workbook = new SXSSFWorkbook(100);// 内存中保留100行
-
日期处理:直接使用
cell.setCellValue(new Date())或setCellValue("yyyy-mm-dd")可能不会显示为你期望的格式,必须先创建一个CellStyle并设置日期格式,然后将这个CellStyle应用到单元格上。 -
单元格类型判断:在读取单元格时,必须使用
cell.getCellType()来判断单元格的数据类型,然后调用相应的getXXXCellValue()方法,不要假设一个单元格里的数字就是Integer或Double,它可能是公式计算的结果,也可能是字符串形式的数字,POI 提供了DataFormatter类来统一格式化单元格的显示值。DataFormatter dataFormatter = new DataFormatter(); String cellValue = dataFormatter.formatCellValue(cell); // 获取单元格显示的值
