目录
- Apache POI 简介
- 环境搭建
- 核心概念:
XSSFWorkbookvsHSSFWorkbook - 基本操作(读取和写入)
- 1 创建 Excel 文件
- 2 读取 Excel 文件
- 3 修改 Excel 文件
- 高级操作
- 1 样式设置(字体、颜色、边框、对齐)
- 2 合并单元格
- 3 列宽和行高
- 4 处理日期
- 5 处理公式
- 大文件处理(SXSSF)
- 最佳实践与注意事项
- 完整示例代码
Apache POI 简介
Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,由 Apache 软件基金会维护,它提供了 API,允许 Java 程序读取和写入 Microsoft Office 格式的文件,如 .xls (Excel 97-2003) 和 .xlsx (Excel 2007+)。

- HSSF: 操作
.xls格式,属于旧版格式,行数限制为 65,536 行,列数为 256 列。 - XSSF: 操作
.xlsx格式,属于新版格式,理论上只受限于内存大小。 - SXSSF: 基于 XSSF 的大文件流式 API,用于处理非常大的 Excel 文件,以减少内存消耗。
- HWPF: 操作
.doc格式。 - XWPF: 操作
.docx格式。
强烈建议: 除非有特殊兼容性要求,否则请优先使用 .xlsx 格式(XSSF),因为它功能更强大、性能更好。
环境搭建
你需要在你的项目中添加 POI 的依赖,如果你使用 Maven,在 pom.xml 中添加以下依赖:
<!-- 使用最新的稳定版本,请访问 Maven Central 查找最新版本 -->
<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>
<!-- 如果需要处理 XML,可能需要这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
如果你使用 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 (BIFF 格式) |
.xlsx (OOXML 格式) |
| 版本 | Excel 97-2003 | Excel 2007 及更高版本 |
| 内存占用 | 所有数据加载到内存 | 所有数据加载到内存 |
| 行/列限制 | 65,536 行 / 256 列 | 1,048,576 行 / 16,384 列 |
| 性能 | 相对较快 | 相对较慢(文件结构更复杂) |
| 功能 | 功能有限 | 功能丰富(支持更多样式、图表等) |
如何选择?
- 如果你的用户可能还在使用非常古老的 Excel 版本(2003或更早),或者生成的文件必须兼容这些旧版本,使用
HSSFWorkbook。 - 对于所有新项目,强烈推荐使用
XSSFWorkbook。
基本操作
1 创建 Excel 文件
下面是一个创建 .xlsx 文件并写入数据的完整示例。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class CreateExcelExample {
public static void main(String[] args) {
// 1. 创建一个 XSSFWorkbook 对象,代表一个 Excel 工作簿
try (Workbook workbook = new XSSFWorkbook()) {
// 2. 创建一个工作表
Sheet sheet = workbook.createSheet("员工信息");
// 3. 创建行 (从0开始)
Row headerRow = sheet.createRow(0);
// 4. 创建单元格并设置值
Cell headerCell1 = headerRow.createCell(0);
headerCell1.setCellValue("姓名");
Cell headerCell2 = headerRow.createCell(1);
headerCell2.setCellValue("年龄");
Cell headerCell3 = headerRow.createCell(2);
headerCell3.setCellValue("部门");
// 5. 创建数据行
Row dataRow1 = sheet.createRow(1);
dataRow1.createCell(0).setCellValue("张三");
dataRow1.createCell(1).setCellValue(28);
dataRow1.createCell(2).setCellValue("技术部");
Row dataRow2 = sheet.createRow(2);
dataRow2.createCell(0).setCellValue("李四");
dataRow2.createCell(1).setCellValue(32);
dataRow2.createCell(2).setCellValue("市场部");
// 6. 写入文件输出流
try (FileOutputStream fileOut = new FileOutputStream("employees.xlsx")) {
workbook.write(fileOut);
System.out.println("Excel 文件 'employees.xlsx' 创建成功!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
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 ReadExcelExample {
public static void main(String[] args) {
String filePath = "employees.xlsx";
try (FileInputStream fileIn = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fileIn)) {
// 1. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0); // 或者 getSheet("员工信息")
// 2. 遍历行 (从0开始,跳过标题行)
for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
continue; // 跳过空行
}
// 3. 获取单元格并读取值
// getCell(0) 是第一列,getCell(1) 是第二列
Cell nameCell = row.getCell(0);
Cell ageCell = row.getCell(1);
Cell deptCell = row.getCell(2);
// 处理单元格类型,避免 getCellValue() 返回类型不确定的问题
String name = nameCell.getStringCellValue();
int age = (int) ageCell.getNumericCellValue(); // 年龄是数字
String department = deptCell.getStringCellValue();
System.out.println("姓名: " + name + ", 年龄: " + age + ", 部门: " + department);
}
} 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 ModifyExcelExample {
public static void main(String[] args) {
String inputFilePath = "employees.xlsx";
String outputFilePath = "employees_modified.xlsx";
try (FileInputStream fileIn = new FileInputStream(inputFilePath);
Workbook workbook = new XSSFWorkbook(fileIn);
FileOutputStream fileOut = new FileOutputStream(outputFilePath)) {
Sheet sheet = workbook.getSheetAt(0);
// 修改第二行第二列的年龄
Row rowToModify = sheet.getRow(1); // 第二行
if (rowToModify != null) {
Cell cellToModify = rowToModify.getCell(1); // 第二列
if (cellToModify != null) {
cellToModify.setCellValue(99); // 把年龄改成99
} else {
// 如果单元格不存在,可以创建一个
rowToModify.createCell(1).setCellValue(99);
}
}
// 添加一个新行
int lastRowNum = sheet.getLastRowNum();
Row newRow = sheet.createRow(lastRowNum + 1);
newRow.createCell(0).setCellValue("王五");
newRow.createCell(1).setCellValue(25);
newRow.createCell(2).setCellValue("人事部");
// 将修改后的内容写入新文件
workbook.write(fileOut);
System.out.println("Excel 文件修改成功,已保存为 '" + outputFilePath + "'");
} catch (IOException e) {
e.printStackTrace();
}
}
}
高级操作
1 样式设置
样式通过 CellStyle 对象来创建和设置,然后应用到单元格。
// 在创建工作簿后
CellStyle style = workbook.createCellStyle();
// 1. 设置字体
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 12);
font.setBold(true);
font.setColor(IndexedColors.RED.getIndex());
style.setFont(font);
// 2. 设置背景色
style.setFillForegroundColor(IndexedColors.YELLOW.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); // 垂直居中
// 应用样式到单元格
Cell cell = row.createCell(0);
cell.setCellValue("带样式的文本");
cell.setCellStyle(style);
2 合并单元格
使用 Sheet 的 addMergedRegion 方法。
// 参数 CellRangeAddress 构造函数: (起始行, 结束行, 起始列, 结束列)
// 行和列都是从0开始索引
sheet.addMergedRegion(new CellRangeAddress(0, 2, 0, 0)); // 合并第1到第3行,第1列
// 在合并区域的第一个单元格写入内容
Row row = sheet.getRow(0);
if (row == null) {
row = sheet.createRow(0);
}
row.createCell(0).setCellValue("合并的单元格");
3 列宽和行高
// 设置列宽 (参数: 列索引, 宽度,宽度是字符宽度的1/256) sheet.setColumnWidth(0, 20 * 256); // 设置第一列宽度为20个字符 // 自动调整列宽 (基于内容) // sheet.autoSizeColumn(0); // 对第一列进行自适应 // 注意:autoSizeColumn 在处理大数据量时可能很慢,建议只在少量数据或导出前调用。 // 设置行高 (参数: 行索引, 高度,高度是1/20个点) Row row = sheet.createRow(1); row.setHeightInPoints(30); // 设置行高为30个点
4 处理日期
POI 内部使用 double 类型来存储日期和时间(从1900年1月1日开始的天数小数),你需要使用 DataFormat 来正确显示。
// 创建一个日期单元格
CellStyle dateStyle = workbook.createCellStyle();
DataFormat format = workbook.createDataFormat();
dateStyle.setDataFormat(format.getFormat("yyyy-MM-dd"));
Row row = sheet.createRow(3);
Cell dateCell = row.createCell(0);
java.util.Date date = new java.util.Date(); // 获取当前日期
dateCell.setCellValue(date);
dateCell.setCellStyle(dateStyle);
5 处理公式
只需将公式字符串设置到单元格即可。
Cell formulaCell = row.createCell(4);
formulaCell.setCellFormula("SUM(B1:B2)"); // 计算B1和B2的和
大文件处理(SXSSF)
当处理超过 10,000 行数据时,使用 XSSFWorkbook 会导致内存溢出(OOM)。SXSSF (Streaming Usermodel API) 通过将部分数据写入临时文件来解决此问题,从而显著降低内存使用。
核心特点:
- 低内存消耗:只保留当前可见行(
window size)在内存中。 - 只写:
SXSSF是一个只写 API,不支持随机读取和修改。 - 临时文件:数据会先写入一个临时文件,最后再合并到最终文件中,需要清理临时文件。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class LargeFileExample {
public static void main(String[] args) {
// windowSize 是内存中保留的行数,默认为100
try (Workbook workbook = new SXSSFWorkbook(100)) {
Sheet sheet = workbook.createSheet("大数据");
// 写入10万行数据
for (int i = 0; i < 100000; i++) {
Row row = sheet.createRow(i);
row.createCell(0).setCellValue("数据 " + i);
row.createCell(1).setCellValue(i * 10);
}
// 写入文件
try (FileOutputStream out = new FileOutputStream("large_data.xlsx")) {
workbook.write(out);
}
// 重要:清理由 SXSSF 创建的临时文件
((SXSSFWorkbook) workbook).dispose();
System.out.println("大文件生成完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
最佳实践与注意事项
- 使用
try-with-resources:Workbook,Sheet,Row,FileInputStream,FileOutputStream等资源都应该放在try-with-resources语句块中,以确保它们被正确关闭,防止内存泄漏。 - 优先选择
.xlsx:除非有兼容性要求,否则总是使用XSSFWorkbook。 - 处理大数据量用
SXSSF:如果行数超过 5 万,务必考虑使用SXSSFWorkbook。 - 不要在循环中创建样式:样式对象创建开销较大,应该在循环外部创建好样式,然后在循环中重复使用。
- 单元格类型安全:读取单元格时,使用
getCellType()判断类型,然后调用对应的getXXXCellValue()方法,避免ClassCastException。 - 注意日期格式:日期在 Excel 中是特殊类型,务必使用
DataFormat来设置显示格式。 - 异常处理:文件操作可能会抛出
IOException,务必进行捕获和处理。
完整示例代码
这是一个综合了读取、样式设置、合并单元格、自动调整列宽和写入的完整示例。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class ComprehensiveExample {
public static void main(String[] args) {
String filePath = "comprehensive_report.xlsx";
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("销售报表");
// 1. 创建标题样式
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 16);
titleStyle.setFont(titleFont);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
// 2. 创建表头样式
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
// 3. 创建数据样式
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setBorderTop(BorderStyle.THIN);
dataStyle.setBorderBottom(BorderStyle.THIN);
dataStyle.setBorderLeft(BorderStyle.THIN);
dataStyle.setBorderRight(BorderStyle.THIN);
dataStyle.setAlignment(HorizontalAlignment.RIGHT);
// 4. 创建合并的标题行
Row titleRow = sheet.createRow(0);
titleRow.createCell(0).setCellValue("2025年第四季度销售报告");
titleRow.getCell(0).setCellStyle(titleStyle);
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3)); // 合并第一行的5列
// 5. 创建表头
String[] headers = {"产品名称", "销售数量", "单价", "总销售额"};
Row headerRow = sheet.createRow(1);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 6. 创建数据
Object[][] data = {
{"笔记本电脑", 120, 5999.99},
{"无线鼠标", 350, 99.50},
{"机械键盘", 80, 459.00},
{"显示器", 45, 1299.00}
};
int rowNum = 2;
for (Object[] rowData : data) {
Row row = sheet.createRow(rowNum++);
for (int i = 0; i < rowData.length; i++) {
Cell cell = row.createCell(i);
if (rowData[i] instanceof String) {
cell.setCellValue((String) rowData[i]);
} else if (rowData[i] instanceof Integer) {
cell.setCellValue((Integer) rowData[i]);
} else if (rowData[i] instanceof Double) {
cell.setCellValue((Double) rowData[i]);
}
cell.setCellStyle(dataStyle);
}
// 计算总销售额
Cell totalSalesCell = row.createCell(3);
totalSalesCell.setCellFormula("B" + (rowNum) + "*C" + (rowNum));
totalSalesCell.setCellStyle(dataStyle);
}
// 7. 自动调整列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 8. 写入文件
try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
workbook.write(fileOut);
}
System.out.println("综合示例 Excel 文件 '" + filePath + "' 创建成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
} 