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

- Excel (
.xls,.xlsx) - Word (
.doc,.docx) - PowerPoint (
.ppt,.pptx)
对于 Excel 操作,POI 提供了不同的模型来处理不同版本的 Excel 文件:
| 文件扩展名 | POI 模型 | 描述 |
|---|---|---|
| .xls | HSSF (Horrible SpreadSheet Format) |
用于操作 Excel 97-2003 格式,基于二进制格式,行数和列数限制为 65536 x 256。 |
| .xlsx | XSSF (XML SpreadSheet Format) |
用于操作 Office Open XML (OOXML) 格式,即 Excel 2007 及以后版本,基于 XML 格式,理论上行和列的数量只受内存限制。 |
| .xlsb | SXSSF (Streaming Usermodel API for XSSF) |
用于处理超大 .xlsx 文件,它采用流式处理,将部分数据写入临时文件,从而减少内存消耗。 |
环境配置 (Maven)
你需要在你的项目中添加 Apache POI 的依赖,如果你使用 Maven,请在 pom.xml 文件中添加以下依赖:
<dependencies>
<!-- 核心依赖 -->
<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>
<!-- 为了支持 XSSF 和 SXSSF 的一些高级特性,可能需要这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
基础写入操作 (创建 Excel 文件)
下面是一个完整的示例,演示如何创建一个 .xlsx 文件,并向其中写入数据、设置样式和公式。
目标: 创建一个名为 员工信息.xlsx 的文件,内容如下:

| 姓名 | 部门 | 工资 | 奖金 | 总收入 (公式) |
|---|---|---|---|---|
| 张三 | 技术部 | 8000 | 1000 | =C2+D2 |
| 李四 | 销售部 | 7000 | 2000 | =C3+D3 |
| 王五 | 人事部 | 6000 | 500 | =C4+D4 |
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class ExcelWriteExample {
public static void main(String[] args) {
// 1. 创建一个新的 XSSFWorkbook 对象 (代表一个 .xlsx 文件)
Workbook workbook = new XSSFWorkbook();
// 2. 创建一个 Sheet (工作表)
Sheet sheet = workbook.createSheet("员工信息");
// 3. 创建一个样式,用于表头
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 12);
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 4. 创建行和单元格,并写入表头数据
Row headerRow = sheet.createRow(0); // 第 0 行
String[] headers = {"姓名", "部门", "工资", "奖金", "总收入"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 5. 写入数据行
Object[][] data = {
{"张三", "技术部", 8000, 1000},
{"李四", "销售部", 7000, 2000},
{"王五", "人事部", 6000, 500}
};
for (int i = 0; i < data.length; i++) {
Row row = sheet.createRow(i + 1); // 从第 1 行开始
for (int j = 0; j < data[i].length; j++) {
// 写入姓名、部门、工资、奖金
row.createCell(j).setCellValue(String.valueOf(data[i][j]));
}
// 写入公式
Cell totalCell = row.createCell(4);
totalCell.setCellFormula("C" + (i + 2) + "+D" + (i + 2));
}
// 6. 自动调整列宽,使内容完整显示
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 7. 将 workbook 写入到文件中
try (FileOutputStream fileOut = new FileOutputStream("员工信息.xlsx")) {
workbook.write(fileOut);
System.out.println("Excel 文件 '员工信息.xlsx' 创建成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 8. 关闭 workbook,释放资源
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码解析:
Workbook workbook = new XSSFWorkbook();: 创建一个.xlsx格式的工作簿。Sheet sheet = workbook.createSheet("员工信息");: 创建一个名为 "员工信息" 的工作表。CellStyle和Font: 用于设置单元格的样式,如字体加粗、背景色等。Row row = sheet.createRow(0);: 创建一行,参数 0 表示第一行。Cell cell = row.createCell(0);: 在指定行中创建一个单元格,参数 0 表示第一列。cell.setCellValue("姓名");: 设置单元格的值。cell.setCellStyle(headerStyle);: 为单元格应用之前创建的样式。totalCell.setCellFormula("C2+D2");: 设置单元格的值为 Excel 公式,注意,这里的行号是相对于工作表的,需要动态计算。sheet.autoSizeColumn(i);: 自动调整列宽,让所有内容都能显示出来。FileOutputStream和workbook.write(): 将内存中的Workbook对象写入到磁盘文件。try-with-resources和workbook.close(): 非常重要! 确保文件流和Workbook资源被正确关闭,防止内存泄漏。
基础读取操作 (读取 Excel 文件)
下面是如何读取上面创建的 员工信息.xlsx 文件。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ExcelReadExample {
public static void main(String[] args) {
String filePath = "员工信息.xlsx";
try (FileInputStream fileIn = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fileIn)) {
// 1. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0); // 或者 getSheet("员工信息")
// 2. 获取总行数
int rowCount = sheet.getPhysicalNumberOfRows();
System.out.println("总行数: " + rowCount);
// 3. 遍历每一行
for (int r = 0; r < rowCount; r++) {
Row row = sheet.getRow(r);
if (row == null) {
continue; // 跳过空行
}
// 4. 获取总列数
int cellCount = row.getPhysicalNumberOfCells();
// System.out.println("第 " + (r+1) + " 行有 " + cellCount + " 列");
// 5. 遍历每一个单元格
StringBuilder rowData = new StringBuilder();
for (int c = 0; c < cellCount; c++) {
Cell cell = row.getCell(c);
if (cell == null) {
rowData.append("\t"); // 单元格为空,用制表符分隔
continue;
}
// 根据单元格类型获取值
switch (cell.getCellType()) {
case STRING:
rowData.append(cell.getStringCellValue()).append("\t");
break;
case NUMERIC:
// 检查是否是日期格式
if (DateUtil.isCellDateFormatted(cell)) {
rowData.append(cell.getDateCellValue()).append("\t");
} else {
rowData.append(cell.getNumericCellValue()).append("\t");
}
break;
case BOOLEAN:
rowData.append(cell.getBooleanCellValue()).append("\t");
break;
case FORMULA:
// 获取公式计算后的结果
rowData.append(cell.getNumericCellValue()).append("\t");
break;
case BLANK:
rowData.append("\t");
break;
default:
rowData.append("未知类型\t");
}
}
System.out.println(rowData.toString().trim());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码解析:
FileInputStream: 用于读取文件。Workbook workbook = new XSSFWorkbook(fileIn);: 将文件流加载到Workbook对象中。sheet.getSheetAt(0): 获取第一个工作表。sheet.getPhysicalNumberOfRows(): 获取工作表中包含数据的行数。row.getCell(c): 获取指定位置的单元格。cell.getCellType(): 非常重要! 必须先判断单元格的类型,再调用对应的方法获取值,数字单元格用getNumericCellValue(),字符串单元格用getStringCellValue()。DateUtil.isCellDateFormatted(cell): 判断一个数字格式的单元格是否代表日期。cell.getNumericCellValue(): 对于公式单元格,此方法会返回公式的计算结果。
高级技巧与注意事项
1 处理超大文件 (SXSSF)
当处理成千上万行数据时,XSSF 会消耗大量内存,甚至导致 OutOfMemoryError,这时应该使用 SXSSF (Streaming API)。
核心思想: SXSSF 只保留一定数量的行在内存中(100 行),当超过这个数量时,最早的一批行会被写入临时文件,这极大地降低了内存消耗。
// 使用 SXSSFWorkbook
// 参数 100 表示在内存中保留的行数
Workbook workbook = new SXSSFWorkbook(100);
// ... 写入数据的代码与 XSSF 基本相同 ...
// 写入完成后,必须调用 write() 和 dispose() 来清理临时文件
try (FileOutputStream out = new FileOutputStream("large_file.xlsx")) {
workbook.write(out);
} finally {
((SXSSFWorkbook) workbook).dispose(); // 清理临时文件
}
2 设置单元格样式
除了字体和背景色,你还可以设置很多样式:
CellStyle style = workbook.createCellStyle();
// 对齐方式
style.setAlignment(HorizontalAlignment.CENTER); // 水平居中
style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
// 边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 背景色
style.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 日期格式
CreationHelper createHelper = workbook.getCreationHelper();
style.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd hh:mm:ss"));
// 应用样式
cell.setCellStyle(style);
3 合并单元格
// 参数:起始行, 结束行, 起始列, 结束列 sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 4)); // 合并第 0 行的 0 到 4 列 // 然后在合并区域的左上角单元格写入内容即可
4 处理不同日期格式
Excel 内部将日期存储为一个数字(从 1900/1/1 开始的天数),你需要用 DataFormat 来告诉 Excel 如何显示这个数字。
CellStyle dateStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy年mm月dd日"));
Cell dateCell = row.createCell(0);
dateCell.setCellValue(new Date()); // 设置当前日期
dateCell.setCellStyle(dateStyle); // 应用日期格式
5 性能建议
- 减少样式对象创建:如果多个单元格需要相同的样式,请复用
CellStyle对象,而不是为每个单元格都创建一个新的。 - 关闭资源:始终使用
try-with-resources或在finally块中关闭Workbook和FileOutputStream。 - 选择合适的 API:处理小文件用
XSSF,处理大文件用SXSSF。 - 避免频繁的行/列操作:在写入大量数据前,可以先规划好行和列,而不是动态地创建。
希望这份详细的指南能帮助你熟练掌握使用 Java 和 Apache POI 操作 Excel!
