目录
- 什么是 Apache POI?
- 准备工作:添加 Maven 依赖
- 核心概念:
Workbook,Sheet,Row,Cell - 完整代码示例:创建、读取、更新、删除
- 常用操作详解
- 创建新 Excel 文件
- 读取现有 Excel 文件
- 写入数据(单元格样式、日期、公式)
- 处理不同版本的 Excel (
.xlsvs.xlsx)
- 性能优化与最佳实践
- SXSSF (流式 API):处理大数据量
- 事件模型 (SAX):解析超大文件
- 常见问题与解决方案
OutOfMemoryError错误- 读取
.xlsx文件报错Invalid header signature - 单元格样式问题
什么是 Apache POI?
Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,由 Apache 软件基金会维护,它允许 Java 程序读取、写入和操作 Microsoft Office 格式的文件,其中最核心的就是 Excel。

主要支持两种格式的 Excel:
.xls: Excel 97-2003 格式,使用HSSF(Horrible Spreadsheet Format) API。.xlsx: Excel 2007 及以后版本,基于 Office Open XML 格式,使用XSSFAPI。
准备工作:添加 Maven 依赖
在你的 pom.xml 文件中添加 POI 的依赖,为了同时支持 .xls 和 .xlsx,我们通常会添加 poi 和 poi-ooxml。
<dependencies>
<!-- 支持 .xls 格式 -->
<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,是 poi-ooxml 的依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
核心概念
理解 POI 操作 Excel 的核心思想至关重要,就像操作一个数据库表。
Workbook(工作簿): 代表一个 Excel 文件,它是最高层级的对象。- 对于
.xls文件,实现类是HSSFWorkbook。 - 对于
.xlsx文件,实现类是XSSFWorkbook。
- 对于
Sheet(工作表): 代表Workbook中的一个工作表,"Sheet1", "Sheet2"。Row(行): 代表Sheet中的某一行,行号从0开始。Cell(单元格): 代表Row中的某一个单元格,列号也从0开始。
操作流程:获取 Workbook -> 选择 Sheet -> 选择 Row -> 操作 Cell。

完整代码示例
这个示例将演示如何创建一个 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;
import java.util.Date;
public class PoiDemo {
public static void main(String[] args) {
String filePath = "D:/temp/poi_demo.xlsx";
// 1. 创建 Excel 并写入数据
createExcel(filePath);
// 2. 读取 Excel 数据
readExcel(filePath);
// 3. 更新 Excel 数据
updateExcel(filePath);
}
/**
* 创建一个新的 Excel 文件并写入数据
*/
public static void createExcel(String filePath) {
// XSSFWorkbook 用于处理 .xlsx 文件
Workbook workbook = new XSSFWorkbook();
// HSSFWorkbook 用于处理 .xls 文件
// Workbook workbook = new HSSFWorkbook();
try (FileOutputStream fos = new FileOutputStream(filePath)) {
// 创建一个工作表
Sheet sheet = workbook.createSheet("员工信息");
// 创建表头
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(28);
// 处理日期类型
Cell dateCell = dataRow1.createCell(3);
dateCell.setCellValue(new Date());
// 设置日期格式
CellStyle dateStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
dateCell.setCellStyle(dateStyle);
// 创建数据行
Row dataRow2 = sheet.createRow(2);
dataRow2.createCell(0).setCellValue(2);
dataRow2.createCell(1).setCellValue("李四");
dataRow2.createCell(2).setCellValue(30);
dataRow2.createCell(3).setCellValue(new Date());
// 自动调整列宽,以适应内容
for (int i = 0; i < 4; i++) {
sheet.autoSizeColumn(i);
}
// 写入文件
workbook.write(fos);
System.out.println("Excel 文件创建成功!路径: " + filePath);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取一个 Excel 文件
*/
public static void readExcel(String filePath) {
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = WorkbookFactory.create(fis)) {
// 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 遍历每一行
for (Row row : sheet) {
// 遍历每一个单元格
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("UNKNOWN\t");
}
}
System.out.println(); // 换行
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 更新一个已有的 Excel 文件
*/
public static void updateExcel(String filePath) {
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = WorkbookFactory.create(fis);
FileOutputStream fos = new FileOutputStream(filePath)) {
Sheet sheet = workbook.getSheetAt(0);
// 获取第3行(索引为2)
Row row = sheet.getRow(2);
// 获取第2列(索引为1)的单元格,如果不存在则创建
Cell cell = row.getCell(1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
// 更新值
cell.setCellValue("王五");
// 写回文件
workbook.write(fos);
System.out.println("Excel 文件更新成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
常用操作详解
创建新 Excel 文件
// 1. 创建 Workbook 对象
Workbook workbook = new XSSFWorkbook(); // .xlsx
// Workbook workbook = new HSSFWorkbook(); // .xls
// 2. 创建 Sheet
Sheet sheet = workbook.createSheet("新工作表");
// 3. 创建 Row
Row row = sheet.createRow(0); // 第一行
// 4. 创建 Cell 并赋值
Cell cell = row.createCell(0);
cell.setCellValue("Hello, POI!");
// 5. 写入文件
try (FileOutputStream fos = new FileOutputStream("output.xlsx")) {
workbook.write(fos);
}
读取现有 Excel 文件
使用 WorkbookFactory.create() 是最通用的方法,它可以自动识别 .xls 还是 .xlsx。
try (FileInputStream fis = new FileInputStream("data.xlsx");
Workbook workbook = WorkbookFactory.create(fis)) {
Sheet sheet = workbook.getSheet("Sheet1");
// 或者 sheet = workbook.getSheetAt(0); // 按索引获取
// 获取总行数
int rowCount = sheet.getPhysicalNumberOfRows();
// 获取总列数 (以第一行为准)
int colCount = sheet.getRow(0).getPhysicalNumberOfCells();
for (int i = 0; i < rowCount; i++) {
Row row = sheet.getRow(i);
for (int j = 0; j < colCount; j++) {
Cell cell = row.getCell(j);
// ... (使用 switch-case 处理不同类型)
}
}
}
写入数据(单元格样式、日期、公式)
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("样式示例");
// 创建样式
CellStyle style = workbook.createCellStyle();
// 背景颜色
style.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 字体
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 14);
style.setFont(font);
// 边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 水平居中
style.setAlignment(HorizontalAlignment.CENTER);
// 应用样式
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("这是带样式的单元格");
cell.setCellStyle(style);
// 写入公式
Cell formulaCell = row.createCell(1);
formulaCell.setCellFormula("SUM(A1:A10)"); // 假设 A1 到 A10 有数字
// 写入日期
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy年MM月dd日"));
Cell dateCell = row.createCell(2);
dateCell.setCellValue(new Date());
dateCell.setCellStyle(dateStyle);
处理不同版本的 Excel
| 文件扩展名 | POI API | 内存模型 | 备注 |
|---|---|---|---|
.xls |
HSSFWorkbook |
所有数据加载到内存 | 最多支持 65536 行,256 列,旧版本。 |
.xlsx |
XSSFWorkbook |
所有数据加载到内存 | 理论上支持无限行和列,但大文件会导致 OOM。 |
.xlsx (大数据) |
SXSSFWorkbook |
磁盘缓存(滑动窗口) | 专门为处理大数据量设计,避免 OOM,推荐用于导出。 |
.xls (大数据) |
HSSFWorkbook 无对应 |
无 | POI 没有为 .xls 提供流式 API,大数据量建议转用 .xlsx。 |
性能优化与最佳实践
SXSSF (流式 API) - 处理大数据量导出
当需要导出几十万甚至上百万行数据时,使用 XSSFWorkbook 会导致内存溢出。SXSSFWorkbook (Streaming Usermodel API) 通过将数据写入临时文件,只在内存中保留一部分数据(一个滑动窗口),从而极大地减少了内存消耗。
核心特点:

- 低内存: 内存占用与行数无关,只与窗口大小有关。
- 只写:
SXSSFWorkbook主要用于写操作,读操作仍然使用XSSF或HSSF。 - 临时文件: 默认情况下,临时文件会保存在系统临时目录 (
java.io.tmpdir),记得在完成后清理。
示例:导出 10 万行数据
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFCell;
public class BigDataExport {
public static void main(String[] args) throws Exception {
// 1. 创建 SXSSFWorkbook,参数是窗口大小(在内存中保留的行数)
// -1 表示使用默认值 (100)
Workbook workbook = new SXSSFWorkbook(100); // window size
Sheet sheet = workbook.createSheet("大数据");
// 2. 像使用 XSSFWorkbook 一样写入数据
for (int rownum = 0; rownum < 100000; rownum++) {
Row row = sheet.createRow(rownum);
for (int cellnum = 0; cellnum < 10; cellnum++) {
Cell cell = row.createCell(cellnum);
cell.setCellValue("Row " + rownum + ", Cell " + cellnum);
}
// 可以手动将行刷出到磁盘
if (rownum % 100 == 0) {
((SXSSFSheet) sheet).flushRows(); // 100 rows in memory at any time
}
}
// 3. 写入文件
try (FileOutputStream out = new FileOutputStream("D:/temp/big_data.xlsx")) {
workbook.write(out);
}
// 4. 重要!清理临时文件
((SXSSFWorkbook) workbook).dispose();
System.out.println("大数据导出完成!");
}
}
事件模型 (SAX) - 解析超大文件
如果只是需要读取一个超大 Excel 文件(1GB 以上),并且只需要其中一部分数据,使用 XSSF 仍然会占用大量内存,这时可以使用 SAX (Simple API for XML) 事件模型。
它不会将整个文件加载到内存,而是像解析 XML 一样,逐行触发事件(如 startRow, endRow, cellValue 等),你只需要监听这些事件并处理数据即可。
这种方式性能最高,但编程模型最复杂,需要自己处理事件,通常用于 ETL 或数据迁移场景。
常见问题与解决方案
OutOfMemoryError 错误
- 原因: 使用
XSSFWorkbook或HSSFWorkbook处理大文件时,所有数据都被加载到 JVM 堆内存中。 - 解决方案:
- 首选: 使用
SXSSFWorkbook进行大数据量导出。 - 次选: 使用 SAX 事件模型进行大数据量读取。
- 增加JVM堆内存 (
-Xmx1024m等),这只是治标不治本,不推荐。
- 首选: 使用
读取 .xlsx 文件报错 Invalid header signature
- 原因: 通常是因为
.xlsx文件正在被 Excel 软件打开,导致文件被锁定或损坏。 - 解决方案: 关闭 Excel 软件,确保文件没有被其他程序占用。
单元格样式问题(如数字变成科学计数法)
- 原因: POI 默认不会为数字单元格设置格式,当数字过长时,它会自动转换为科学计数法或文本格式。
- 解决方案: 在写入数字之前,手动为单元格设置一个合适的样式,
CellStyle的setDataFormat()。
CellStyle style = workbook.createCellStyle();
style.setDataFormat(workbook.createDataFormat().getFormat("@")); // @ 表示文本格式
cell.setCellStyle(style);
cell.setCellValue(1234567890123L); // 长数字
| 场景 | 推荐的 POI API | 特点 |
|---|---|---|
| 读取/写入小/中文件 | XSSFWorkbook / HSSFWorkbook |
简单易用,功能完整 |
导出大数据文件 (.xlsx) |
SXSSFWorkbook |
内存友好,流式写入,避免 OOM |
读取超大文件 (.xlsx) |
SAX (事件模型) | 性能最高,内存占用极低,编程复杂 |
对于大多数日常应用,掌握 XSSFWorkbook 和 SXSSFWorkbook 就足以应对所有挑战,希望这份详细的指南能帮助你熟练地在 Java 中使用 POI 操作 Excel!
