目录
- 准备工作:引入 POI 依赖
- 核心概念:
Workbook,Sheet,Row,Cell - 完整代码示例:读取
.xlsx(Excel 2007+) 文件 - 处理不同版本的 Excel (
.xlsvs.xlsx) - 读取单元格数据(处理不同类型)
- 读取复杂文件(合并单元格、公式、日期等)
- 最佳实践与性能优化
- 常见问题与解决方案 (FAQ)
准备工作:引入 POI 依赖
你需要在你的项目中添加 Apache POI 的依赖,如果你使用 Maven,在 pom.xml 文件中添加以下依赖:

<dependencies>
<!-- Apache POI Core -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.4</version> <!-- 建议使用较新版本 -->
</dependency>
<!-- 用于处理 .xlsx (OOXML) 格式 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.4</version>
</dependency>
<!-- 用于处理 .xls (BIFF) 格式 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.4</version>
</dependency>
<!-- 为了处理 XML 和一些依赖,通常需要这个 -->
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>5.1.1</version>
</dependency>
<!-- POI 依赖的日志库 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.7</version>
</dependency>
</dependencies>
注意:
.xlsx是目前的主流格式,对应的 POI 主类是XSSFWorkbook。.xls是旧版格式,对应HSSFWorkbook,代码中需要根据文件后缀选择不同的实现类。
核心概念:Workbook, Sheet, Row, Cell
POI 操作 Excel 的对象模型就像一个树状结构:
Workbook(工作簿):代表一个 Excel 文件,它是整个 Excel 文件的入口,你可以通过Workbook获取所有的Sheet。Sheet(工作表):代表 Excel 文件中的一个工作表,"Sheet1",你可以通过Sheet获取所有的Row。Row(行):代表工作表中的一行,你可以通过Row获取所有的Cell。Cell(单元格):代表行中的一个单元格,是存储数据的最小单位,你可以从Cell中获取具体的数据值。
这个关系是:Workbook -> Sheet -> Row -> Cell。
完整代码示例:读取 .xlsx (Excel 2007+) 文件
假设我们有一个名为 students.xlsx 的文件,内容如下:

| 姓名 | 年龄 | 班级 | 入学日期 |
|---|---|---|---|
| 张三 | 20 | 一班 | 2025-09-01 |
| 李四 | 21 | 二班 | 2025-09-01 |
| 王五 | 19 | 一班 | 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. 定义文件路径
String filePath = "path/to/your/students.xlsx"; // 请替换为你的文件路径
// 使用 try-with-resources 语句自动关闭资源
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) { // 根据文件类型选择 Workbook
// 2. 获取第一个工作表 (Sheet)
Sheet sheet = workbook.getSheetAt(0); // 索引从0开始
// 3. 获取标题行 (第一行),用于获取列名
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
System.out.println("Excel文件为空或没有标题行!");
return;
}
// 4. 遍历数据行 (从第二行开始)
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue; // 跳过空行
}
// 5. 获取单元格数据
Cell nameCell = row.getCell(0);
Cell ageCell = row.getCell(1);
Cell classCell = row.getCell(2);
Cell dateCell = row.getCell(3);
// 6. 读取单元格值并处理
String name = getCellValueAsString(nameCell);
Integer age = getCellValueAsInteger(ageCell);
String className = getCellValueAsString(classCell);
Date admissionDate = getCellValueAsDate(dateCell);
// 7. 打印或处理数据
System.out.println("姓名: " + name + ", 年龄: " + age + ", 班级: " + className + ", 入学日期: " + admissionDate);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将单元格内容转换为字符串
*/
private static String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
// 处理数字可能是日期的情况
if (DateUtil.isCellDateFormatted(cell)) {
return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
} else {
// 普通数字,可以格式化输出,如保留整数
return String.valueOf((int) cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 如果是公式,可以获取公式的计算结果
return String.valueOf(cell.getNumericCellValue());
case BLANK:
return "";
default:
return "";
}
}
/**
* 将单元格内容转换为整数
*/
private static Integer getCellValueAsInteger(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case NUMERIC:
return (int) cell.getNumericCellValue();
case STRING:
try {
return Integer.parseInt(cell.getStringCellValue().trim());
} catch (NumberFormatException e) {
return null;
}
default:
return null;
}
}
/**
* 将单元格内容转换为日期
*/
private static Date getCellValueAsDate(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue();
} else {
// 如果数字被存储为日期,但未格式化,需要转换
return DateUtil.getJavaDate(cell.getNumericCellValue());
}
case STRING:
try {
// 尝试解析字符串格式的日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(cell.getStringCellValue());
} catch (Exception e) {
return null;
}
default:
return null;
}
}
}
处理不同版本的 Excel (.xls vs .xlsx)
代码中 Workbook 的创建方式决定了它处理的 Excel 版本。
.xlsx(Office 2007+): 使用XSSFWorkbookWorkbook workbook = new XSSFWorkbook(new FileInputStream("file.xlsx"));.xls(Office 97-2003): 使用HSSFWorkbookWorkbook workbook = new HSSFWorkbook(new FileInputStream("file.xls"));
你可以通过文件后缀名来判断使用哪个类,但更通用的方法是直接用 WorkbookFactory,它会自动识别文件类型。
// 更通用的方式,自动识别 .xls 或 .xlsx
try (FileInputStream fis = new FileInputStream("file.xlsx");
Workbook workbook = WorkbookFactory.create(fis)) {
// ... 后续代码完全相同
}
WorkbookFactory 是 POI 推荐的方式,因为它更灵活。
读取单元格数据(处理不同类型)
单元格的数据类型是多样的,Cell 对象提供了 getCellType() 方法来获取类型,常见的类型有:
CellType.STRING: 字符串CellType.NUMERIC: 数字(包括日期、时间)CellType.BOOLEAN: 布尔值CellType.FORMULA: 公式CellType.BLANK: 空单元格CellType.ERROR: 错误
如何区分数字和日期?
对于 CellType.NUMERIC 类型的单元格,你需要使用 DateUtil.isCellDateFormatted(cell) 来判断它是否是一个被格式化为日期的单元格,如果是,则用 cell.getDateCellValue() 获取;否则,用 cell.getNumericCellValue() 获取。
Cell cell = row.getCell(0);
if (cell.getCellType() == CellType.NUMERIC) {
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
System.out.println("日期: " + date);
} else {
double number = cell.getNumericCellValue();
System.out.println("数字: " + number);
}
}
读取复杂文件(合并单元格、公式、日期等)
合并单元格
合并单元格的值只存在于左上角的那个单元格中,你需要先检查某个单元格是否是合并单元格,如果是,则获取其合并后的值。
// 假设在循环中
Cell cell = row.getCell(0);
// 检查单元格是否在合并单元格区域内
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
if (mergedRegion.isInRange(cell.getRowIndex(), cell.getColumnIndex())) {
// 如果是,则获取合并区域左上角的单元格的值
Row firstRow = sheet.getRow(mergedRegion.getFirstRow());
Cell firstCell = firstRow.getCell(mergedRegion.getFirstColumn());
String mergedValue = firstCell.getStringCellValue();
System.out.println("合并单元格的值: " + mergedValue);
break;
}
}
公式
对于包含公式的单元格,默认情况下 cell.getStringCellValue() 或 cell.getNumericCellValue() 返回的是公式的计算结果,如果你想获取公式本身,可以使用 cell.getCellFormula()。
Cell formulaCell = row.getCell(4);
if (formulaCell.getCellType() == CellType.FORMULA) {
String formula = formulaCell.getCellFormula();
System.out.println("公式: " + formula);
// 获取计算结果
double result = formulaCell.getNumericCellValue();
System.out.println("计算结果: " + result);
}
最佳实践与性能优化
-
使用
try-with-resources:务必使用try-with-resources语句来包裹FileInputStream和Workbook对象,确保它们在使用完毕后能被自动关闭,避免资源泄漏。try (FileInputStream fis = new FileInputStream(...); Workbook workbook = WorkbookFactory.create(fis)) { // ... } catch (IOException e) { // ... } -
只读取需要的
Sheet:如果文件很大,但只需要第一个工作表,使用workbook.getSheet("Sheet1")或workbook.getSheetAt(0),避免加载所有工作表的数据。 -
避免创建不必要的对象:在循环中,尽量重用对象,减少
new操作。 -
考虑 SXSSF (流式 API):对于超大的 Excel 文件(例如几十万行),
XSSFWorkbook会将整个文件加载到内存中,可能导致OutOfMemoryError,这时,应该使用SXSSFWorkbook(Streaming Usermodel API),它会把数据写入临时文件,只保留一部分数据在内存中,从而大大减少内存占用。// 创建一个只保留100行在内存中的SXSSFWorkbook Workbook workbook = new SXSSFWorkbook(100); // ... 写入数据 // 最后需要调用write方法将数据写入输出流,并清理临时文件 ( (SXSSFWorkbook) workbook ).dispose();
常见问题与解决方案 (FAQ)
Q1: java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook
A: 这是因为你的依赖不完整,确保你已经添加了 poi 和 poi-ooxml 这两个核心依赖。
Q2: 如何处理单元格为空的情况?
A: 在读取 Cell 之前,先判断 row.getCell(columnIndex) 是否返回 null,如果返回 null,说明该单元格不存在(可能是空行或空列)。cell 对象不为 null,再判断 cell.getCellType() == CellType.BLANK。
Q3: 为什么读取的数字是科学计数法(如 23E+7)?
A: 因为 cell.getNumericCellValue() 返回的是 double 类型,当数字很大或很小时,Java 会自动使用科学计数法显示,你可以使用 DecimalFormat 来格式化输出。
double number = cell.getNumericCellValue();
DecimalFormat df = new DecimalFormat("#");
System.out.println(df.format(number));
Q4: 为什么读取的日期是数字(如 44197)?
A: 因为 Excel 内部将日期存储为一个从 1900-01-01 开始的天数序列,你需要使用 DateUtil 工具类来转换它。
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
} else {
// 如果没有格式化,但知道它是日期,可以这样转换
Date date = DateUtil.getJavaDate(cell.getNumericCellValue());
}
希望这份详细的指南能帮助你顺利地在 Java 中使用 POI 导入 Excel 文件!
