目录
- Apache POI 简介
- 环境准备
- 核心概念:
XSSFWorkbookvsHSSFWorkbook - 完整代码示例:读取
.xlsx(Excel 2007+) 文件 - 处理不同数据类型
- 读取
.xls(Excel 97-2003) 文件 - 处理大文件(
SXSSFWorkbook) - 最佳实践与常见问题
Apache POI 简介
Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,由 Apache 软件基金会维护,它提供了 API,允许 Java 程序读取和写入 Microsoft Office 格式的文件,包括 Excel、Word、PowerPoint 等。

对于 Excel,POI 主要支持两种格式:
.xls: Excel 97-2003 格式,使用HSSFWorkbook类。.xlsx: Excel 2007 及以上版本格式,使用XSSFWorkbook类。
环境准备
你需要在你的项目中添加 POI 的依赖,如果你使用 Maven,请在 pom.xml 文件中添加以下依赖:
<dependencies>
<!-- 用于处理 .xlsx (Excel 2007+) 格式 -->
<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> <!-- 处理 OOXML 格式 (.xlsx, .docx) 必需 -->
</dependency>
<!-- 用于处理 .xls (Excel 97-2003) 格式 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
<!-- 如果需要处理公式,可以添加这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
注意:poi-ooxml 依赖内部会包含 poi 和 poi-ooxml-lite,所以通常只需要 poi 和 poi-ooxml 就能覆盖大部分场景。
核心概念:XSSFWorkbook vs HSSFWorkbook
XSSFWorkbook: 用于操作.xlsx格式的 Excel 文件,这是基于 OOXML (Office Open XML) 标准的,本质上是一个 ZIP 压缩包,里面包含 XML 文件,POI 解压后读取 XML 来获取数据。HSSFWorkbook: 用于操作.xls格式的 Excel 文件,这是旧的二进制格式。
选择哪个?

- 优先使用
XSSFWorkbook,因为.xlsx是当前主流格式,且支持更大的行数和列数。 - 如果你的应用需要兼容非常旧的 Excel 版本(2003年以前),才需要使用
HSSFWorkbook。
完整代码示例:读取 .xlsx (Excel 2007+) 文件
假设我们有一个名为 student_info.xlsx 的文件,内容如下:
| 姓名 | 年龄 | 班级 | 入学日期 |
|---|---|---|---|
| 张三 | 20 | 一班 | 2025-09-01 |
| 李四 | 21 | 二班 | 2025-09-01 |
| 王五 | 22 | 一班 | 2025-09-01 |
下面是读取这个文件的完整 Java 代码:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExcelReader {
public static void main(String[] args) {
// 1. 定义 Excel 文件路径
String excelFilePath = "path/to/your/student_info.xlsx"; // 请替换为你的文件路径
// 使用 try-with-resources 语句自动关闭资源
try (FileInputStream fis = new FileInputStream(excelFilePath);
Workbook workbook = new XSSFWorkbook(fis)) {
// 2. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0); // 索引从0开始
// 3. 获取标题行 (第一行)
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
System.out.println("Excel 文件为空或没有标题行。");
return;
}
// 4. 遍历数据行 (从第二行开始)
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row dataRow = sheet.getRow(rowIndex);
if (dataRow == null) {
continue; // 跳过空行
}
// 5. 获取单元格数据并处理
String name = getCellValueAsString(dataRow.getCell(0));
int age = (int) dataRow.getCell(1).getNumericCellValue();
String className = getCellValueAsString(dataRow.getCell(2));
Date admissionDate = dataRow.getCell(3).getDateCellValue();
// 格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String formattedDate = sdf.format(admissionDate);
// 打印数据
System.out.printf("姓名: %s, 年龄: %d, 班级: %s, 入学日期: %s%n",
name, age, className, formattedDate);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将单元格内容安全地转换为字符串
* @param cell 单元格对象
* @return 单元格内容的字符串表示
*/
private static String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
// 如果是日期格式的数字,需要特殊处理
if (DateUtil.isCellDateFormatted(cell)) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(cell.getDateCellValue());
} else {
// 处理普通数字,避免出现科学计数法
return String.valueOf((long) cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 如果是公式,可以获取公式的计算结果
return cell.getCellFormula();
case BLANK:
return "";
default:
return "";
}
}
}
代码解析:
try-with-resources:FileInputStream和Workbook都实现了AutoCloseable接口,使用try-with-resources可以确保它们在使用后被自动关闭,避免资源泄漏。Workbook workbook = new XSSFWorkbook(fis): 创建一个XSSFWorkbook对象,传入FileInputStream来加载 Excel 文件。Sheet sheet = workbook.getSheetAt(0): 获取文件中的第一个工作表(Sheet)。getSheet("Sheet1")也可以通过名称获取。sheet.getLastRowNum(): 获取最后一行的索引(从0开始)。Row row = sheet.getRow(i): 获取指定行的Row对象。Cell cell = row.getCell(j): 获取指定列的Cell对象。cell.getCellType(): 判断单元格的数据类型,非常重要!因为 Excel 中一个单元格可能包含字符串、数字、日期、布尔值等。getCellValueAsString方法: 这是一个辅助方法,用于将不同类型的单元格值统一转换为字符串,处理了null、数字、日期等情况,使代码更健壮。
处理不同数据类型
正如代码中所示,处理单元格时必须考虑其类型。Cell 类提供了 getCellType() 方法,返回 CellType 枚举值:
STRING: 文本NUMERIC: 数字或日期BOOLEAN: 布尔值FORMULA: 公式BLANK: 空单元格ERROR: 错误
特别注意数字和日期:
NUMERIC 类型的单元格可能代表一个普通数字(如年龄 20),也可能代表一个日期(如入学日期),POI 提供了 DateUtil.isCellDateFormatted(cell) 来判断一个数字单元格是否被格式化为日期。
读取 .xls (Excel 97-2003) 文件
读取 .xls 文件的代码与 .xlsx 几乎完全相同,只需要将 XSSFWorkbook 替换为 HSSFWorkbook 即可。
// ...
try (FileInputStream fis = new FileInputStream("path/to/your/file.xls");
Workbook workbook = new HSSFWorkbook(fis)) { // 注意这里使用 HSSFWorkbook
// 后续代码与 .xlsx 完全相同
Sheet sheet = workbook.getSheetAt(0);
// ...
}
// ...
处理大文件(SXSSFWorkbook)
当处理非常大的 Excel 文件(包含数十万行数据)时,XSSFWorkbook 会将整个文件加载到内存中,这很容易导致 OutOfMemoryError(内存溢出)。
为了解决这个问题,POI 提供了 SXSSFWorkbook(Streaming Usermodel API),它的工作原理是:
- 在内存中只保留一定数量的行(例如100行)。
- 当行数超过这个限制时,将较早的行临时写入到磁盘上的临时文件中。
- 这样,内存占用始终保持在一个较低的水平。
示例代码:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class LargeExcelReaderExample {
public static void main(String[] args) {
// 模拟一个很大的数据源
int rowCount = 100000; // 假设有10万行数据
// 1. 创建 SXSSFWorkbook,参数指定内存中保留的行数
// -1 表示使用默认值(通常是100)
Workbook workbook = new SXSSFWorkbook(-1);
// 2. 创建工作表
Sheet sheet = workbook.createSheet("Large Data");
// 3. 写入数据(这里只是演示如何写入,读取同理,但通常大文件是生成而不是读取)
for (int i = 0; i < rowCount; i++) {
Row row = sheet.createRow(i);
for (int j = 0; j < 10; j++) {
Cell cell = row.createCell(j);
cell.setCellValue("Row " + i + ", Col " + j);
}
}
// 4. 将数据写入到文件
try (FileOutputStream out = new FileOutputStream("large_file.xlsx")) {
workbook.write(out);
System.out.println("大文件生成成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 重要!必须清理临时文件
((SXSSFWorkbook) workbook).dispose();
}
}
}
注意:SXSSFWorkbook 主要用于生成大文件,对于读取大文件,POI 本身没有提供流式的读取 API,如果遇到读取超大文件且内存不足的问题,可能需要考虑:
- 将文件分割成多个小文件。
- 使用更底层的 API(如
Event API或SAX API),但这会复杂很多。
最佳实践与常见问题
- 始终使用
try-with-resources: 确保文件流和 Workbook 对象被正确关闭。 - 检查
null: 在获取Row或Cell之前,先检查它们是否为null,避免NullPointerException。 - 处理日期类型: 使用
DateUtil.isCellDateFormatted()来区分数字和日期。 - 性能考虑: 对于读取操作,
XSSFWorkbook和HSSFWorkbook的性能差异不大,对于写入操作,SXSSFWorkbook是处理大文件的不二之选。 - 文件路径: 使用绝对路径或确保文件相对于程序运行路径是正确的。
- 文件损坏: Excel 文件格式不正确或已损坏,POI 可能会抛出异常,需要做好异常处理。
| 任务 | 类 | 适用场景 |
|---|---|---|
读取/写入 .xlsx |
XSSFWorkbook |
现代 Excel 文件(2007+),主流选择。 |
读取/写入 .xls |
HSSFWorkbook |
旧版 Excel 文件(97-2003)。 |
生成超大 .xlsx |
SXSSFWorkbook |
生成包含海量数据的 Excel 文件,防止内存溢出。 |
通过以上教程,你应该已经掌握了使用 Apache POI 解析 Excel 文件的基本方法,从简单的读取到处理不同数据类型,再到应对大文件的挑战,这些知识足以应对大多数 Java 操作 Excel 的场景。
