目录
- 环境准备
- 核心概念与 API 简介
- 基础教程:读取
.xlsx(Excel 2007+) 文件- 读取整个工作表内容
- 读取指定单元格数据
- 处理不同类型的数据
- 高级教程:读取
.xls(Excel 97-2003) 文件 - 性能优化与最佳实践
- 完整代码示例
- 常见问题与解决方案
环境准备
你需要在你的 Java 项目中添加 Apache POI 的依赖,如果你使用 Maven,这是最简单的方式。

Maven 依赖 (pom.xml)
你需要同时添加 poi 和 poi-ooxml 两个库。
<dependencies>
<!-- 核心库 -->
<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 和其他 OO 格式,可能需要这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</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' implementation 'org.apache.poi:poi-scratchpad:5.2.5'
核心概念与 API 简介
在开始编码前,了解 POI 的核心类非常重要。
| 类/接口 | 描述 | 备注 |
|---|---|---|
Workbook |
代表整个 Excel 文档(工作簿)。 | 是一个顶层接口,有两个主要实现:HSSFWorkbook 和 XSSFWorkbook。 |
HSSFWorkbook |
用于操作 .xls 格式的 Excel 文件(Excel 97-2003)。 |
由于其基于二进制格式,单个文件最大支持 65536 行、256 列。 |
XSSFWorkbook |
用于操作 .xlsx 格式的 Excel 文件(Excel 2007+)。 |
基于 Office Open XML 格式,理论上支持更大的行数和列数。 |
Sheet |
代表 Workbook 中的一个工作表(页签)。 |
一个 Workbook 可以包含多个 Sheet。 |
Row |
代表 Sheet 中的一行。 |
行号从 0 开始。 |
Cell |
代表 Row 中的一个单元格。 |
列号从 0 开始。 |
DataFormatter |
一个非常实用的工具类,用于获取单元格的显示值,而不管其底层类型是什么。 | 一个单元格是数字格式 "¥#,##0.00",它会返回 "¥1,234.50" 这样的字符串。 |
基础教程:读取 .xlsx (Excel 2007+) 文件
这是目前最主流的 Excel 格式,我们将使用 XSSFWorkbook。

假设你有一个 test.xlsx 文件,内容如下:
| A | B | C | |
|---|---|---|---|
| 1 | 姓名 | 年龄 | 生日 |
| 2 | 张三 | 25 | 1998-05-10 |
| 3 | 李四 | 30 | 1993-01-20 |
示例代码:读取整个工作表内容
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) {
// 1. 定义文件路径
String filePath = "path/to/your/test.xlsx";
try ( // 2. 使用 try-with-resources 自动关闭流
FileInputStream fis = new FileInputStream(filePath);
// 3. 根据 .xlsx 文件创建 XSSFWorkbook 对象
Workbook workbook = new XSSFWorkbook(fis)
) {
// 4. 获取第一个工作表 (Sheet)
Sheet sheet = workbook.getSheetAt(0); // 索引从 0 开始
// 5. 遍历工作表中的每一行
// sheet.iterator() 会跳过空行,如果需要处理所有行(包括空行),使用 sheet.rowIterator()
for (Row row : sheet) {
// 6. 遍历行中的每一个单元格
for (Cell cell : row) {
// 7. 获取单元格的值
// 直接使用 cell.getStringCellValue() 会抛出异常,因为单元格类型可能不同
// 我们使用 switch-case 来处理不同类型的单元格
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("[BLANK]\t");
break;
default:
System.out.print("[UNKNOWN]\t");
}
}
// 每行打印完后换行
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
更优雅的方式:使用 DataFormatter
上面的 switch-case 语句有点繁琐。DataFormatter 可以让代码更简洁。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.DataFormatter; // 引入 DataFormatter
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelWithFormatter {
public static void main(String[] args) {
String filePath = "path/to/your/test.xlsx";
DataFormatter dataFormatter = new DataFormatter(); // 创建 DataFormatter 实例
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) {
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
for (Cell cell : row) {
// dataFormatter.formatCellValue(cell) 会智能地返回单元格的显示值
// 数字返回字符串形式的数字,日期返回字符串形式的日期
System.out.print(dataFormatter.formatCellValue(cell) + "\t");
}
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:DataFormatter 返回的都是 String 类型,如果你需要进行数学运算,仍然需要将字符串转换为相应的数字类型。
处理不同类型的数据
从上面的代码可以看出,正确处理单元格类型是关键。

CellType |
描述 | 获取方法 |
|---|---|---|
STRING |
字符串 | cell.getStringCellValue() |
NUMERIC |
数字或日期 | cell.getNumericCellValue()配合 DateUtil.isCellDateFormatted(cell) 判断是否为日期 |
BOOLEAN |
布尔值 | cell.getBooleanCellValue() |
FORMULA |
公式 | cell.getCellFormula() (获取公式字符串)需要 FormulaEvaluator 来计算结果 |
BLANK |
空单元格 | - |
ERROR |
错误 | - |
如何处理公式?
// ...
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
// ...
for (Cell cell : row) {
if (cell.getCellType() == CellType.FORMULA) {
// 计算公式的结果
CellValue cellValue = evaluator.evaluate(cell);
switch (cellValue.getCellType()) {
case NUMERIC:
System.out.print(cellValue.getNumberValue() + "\t");
break;
case STRING:
System.out.print(cellValue.getStringValue() + "\t");
break;
// ... 其他类型
}
} else {
// 非公式单元格,按原方式处理
System.out.print(dataFormatter.formatCellValue(cell) + "\t");
}
}
// ...
高级教程:读取 .xls (Excel 97-2003) 文件
读取 .xls 文件与 .xlsx 文件几乎完全相同,唯一的区别是创建 Workbook 的方式。
// ...
// 1. 文件路径
String filePath = "path/to/your/test.xls";
try (FileInputStream fis = new FileInputStream(filePath);
// 2. 使用 HSSFWorkbook 而不是 XSSFWorkbook
Workbook workbook = new HSSFWorkbook(fis)) {
// 3. 后续代码与读取 .xlsx 完全一样
Sheet sheet = workbook.getSheetAt(0);
DataFormatter dataFormatter = new DataFormatter();
// ... 遍历行和单元格
// ...
为了代码的通用性,你可以根据文件后缀名来决定使用哪个实现。
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public Workbook getWorkbook(String filePath) throws IOException {
FileInputStream fis = new FileInputStream(filePath);
if (filePath.endsWith(".xlsx")) {
return new XSSFWorkbook(fis);
} else if (filePath.endsWith(".xls")) {
return new HSSFWorkbook(fis);
}
fis.close();
throw new IllegalArgumentException("文件格式不支持,必须是 .xls 或 .xlsx");
}
性能优化与最佳实践
处理大型 Excel 文件时,内存消耗可能会很高。
-
使用
SXSSF(Streaming Usermodel) for.xlsx:XSSFWorkbook会将整个 Excel 文件加载到内存中,如果文件很大(比如几百兆),会导致OutOfMemoryError。SXSSFWorkbook是XSSFWorkbook的流式版本,它只在内存中保留一定数量的行(默认 100),其余的行会临时写入磁盘,这极大地减少了内存使用。- 注意:
SXSSFWorkbook是只写的,但在读取时,它也可以作为一种更节省内存的选择,因为它不会一次性加载所有数据。
// SXSSFWorkbook 主要用于大数据量写入,但在读取时,如果内存是瓶颈, // 可以配合 XSSFEventBasedModel 或其他低内存 API。 // 对于一般读取,XSSFWorkbook 如果文件不大,仍然是最方便的。
-
总是使用
try-with-resources:Workbook,Sheet,Row,FileInputStream等资源都需要被正确关闭。try-with-resources语句可以确保这些资源在代码块执行完毕后自动关闭,避免内存泄漏。
-
避免不必要的对象创建:
- 如果在循环中创建
DataFormatter或FormulaEvaluator,可以将它们移到循环外部。
- 如果在循环中创建
-
只读取需要的行:
- 如果你知道数据从第 10 行开始,就不要从第 0 行开始遍历,可以使用
sheet.getFirstRowNum()和sheet.getLastRowNum()来获取数据范围。
- 如果你知道数据从第 10 行开始,就不要从第 0 行开始遍历,可以使用
完整代码示例
这是一个更完整的示例,它封装了读取逻辑,并处理了 .xls 和 .xlsx 两种格式。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.DataFormatter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ExcelReader {
/**
* 读取 Excel 文件,并将所有行数据转换为 List<List<String>>
* @param filePath Excel 文件路径
* @return 包含所有行数据的列表
*/
public List<List<String>> readExcel(String filePath) {
List<List<String>> data = new ArrayList<>();
DataFormatter dataFormatter = new DataFormatter();
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = getWorkbook(filePath, fis)) {
Sheet sheet = workbook.getSheetAt(0); // 读取第一个工作表
for (Row row : sheet) {
List<String> rowData = new ArrayList<>();
for (Cell cell : row) {
// 使用 DataFormatter 获取单元格的显示值
rowData.add(dataFormatter.formatCellValue(cell));
}
data.add(rowData);
}
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
/**
* 根据 文件后缀名 创建相应的 Workbook 对象
*/
private Workbook getWorkbook(String filePath, FileInputStream fis) throws IOException {
if (filePath.endsWith(".xlsx")) {
return new XSSFWorkbook(fis);
} else if (filePath.endsWith(".xls")) {
return new HSSFWorkbook(fis);
}
throw new IllegalArgumentException("不支持的文件类型: " + filePath);
}
public static void main(String[] args) {
ExcelReader reader = new ExcelReader();
String excelPath = "path/to/your/test.xlsx"; // 替换为你的文件路径
List<List<String>> excelData = reader.readExcel(excelPath);
// 打印读取到的数据
for (List<String> row : excelData) {
System.out.println(row);
}
}
}
常见问题与解决方案
Q1: java.lang.NoClassDefFoundError: org/openxmlformats/schemas/spreadsheetml/x2006/main/CTWorksheet
- 原因: 你试图读取
.xlsx文件,但只添加了poi依赖,缺少poi-ooxml依赖。 - 解决: 确保
poi-ooxml依赖已正确添加到你的项目中。
Q2: java.lang.OutOfMemoryError: Java heap space
- 原因: Excel 文件过大,
XSSFWorkbook将整个文件加载到内存中,导致内存溢出。 - 解决:
- 增加 JVM 堆内存大小(
-Xmx1024m等),这只是治标不治本。 - 使用
SXSSFWorkbook(主要用于写入)或研究XSSF和SAX(Event API) 结合的低内存读取方式,对于大多数业务场景,优化读取逻辑或分批处理数据更实际。
- 增加 JVM 堆内存大小(
Q3: 读取的日期是数字(如 43586)
- 原因: Excel 内部将日期存储为从 1900/1/1 开始的天数数字,你需要用
DataFormatter或手动转换。 - 解决:
- 简单方法: 使用
DataFormatter,它会自动将日期格式化为字符串。 - 手动转换:
if (DateUtil.isCellDateFormatted(cell)) { Date date = cell.getDateCellValue(); // 使用 SimpleDateFormat 将 Date 转换为想要的字符串格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); System.out.print(sdf.format(date) + "\t"); }
- 简单方法: 使用
Q4: 读取的数字是科学计数法(如 1.23E+7)
- 原因: Excel 的单元格格式被设置为科学计数法。
- 解决:
- 简单方法: 使用
DataFormatter,它会返回单元格显示的文本,即 "12300000"。 - 手动转换: 你需要先获取原始数字值
cell.getNumericCellValue(),然后使用DecimalFormat等工具类进行格式化。
- 简单方法: 使用
希望这份详细的指南能帮助你顺利地在 Java 项目中使用 POI 导入 Excel!
