本教程将涵盖从最基础的 .xls (Excel 97-2003) 到现代的 .xlsx (Excel 2007+) 格式,并提供从简单到复杂的完整代码示例。

目录
- 准备工作:添加 POI 依赖
- 核心概念:
Workbook,Sheet,Row,Cell - 读取
.xls文件 (HSSFWorkbook) - 读取
.xlsx文件 (XSSFWorkbook) - 统一 API 读取任意格式 (XSSF/HSSF)
- 进阶:处理不同数据类型的单元格
- 进阶:读取大文件(SAX 模式,避免内存溢出)
- 完整示例代码
- 总结与最佳实践
准备工作:添加 POI 依赖
你需要在你的项目中添加 POI 的依赖,如果你使用 Maven,请在 pom.xml 文件中添加以下依赖:
<!-- 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>
<!-- 为了支持一些新特性,可能还需要这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version>
</dependency>
如果你使用 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-ooxml-lite:5.2.5'
核心概念
理解 POI 的对象模型是关键,它就像一个树形结构:
Workbook(工作簿):代表整个 Excel 文件,对于.xls,它是HSSFWorkbook;对于.xlsx,它是XSSFWorkbook。Sheet(工作表):代表 Excel 文件中的一个工作表(Sheet1, Sheet2)。Row(行):代表工作表中的一行,行号从 0 开始。Cell(单元格):代表行中的一个单元格,列号也从 0 开始。
读取流程就是:Workbook -> Sheet -> Row -> Cell。

读取 .xls 文件 (HSSFWorkbook)
.xls 是旧版的 Excel 格式,使用 HSSFWorkbook 类处理。
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadXlsExample {
public static void main(String[] args) {
// 1. 定义文件路径
String filePath = "path/to/your/data.xls";
// 使用 try-with-resources 语句,确保文件流被自动关闭
try (FileInputStream fis = new FileInputStream(filePath);
// 2. 创建 HSSFWorkbook 对象,整个 Excel 文件加载到内存
Workbook workbook = new HSSFWorkbook(fis)) {
// 3. 获取第一个工作表 (Sheet)
Sheet sheet = workbook.getSheetAt(0); // 0 表示第一个工作表
// 4. 遍历工作表中的每一行
for (Row row : sheet) {
// 5. 遍历行中的每一个单元格
for (Cell cell : row) {
// 6. 获取单元格的值并打印
// 注意:直接使用 cell.getStringCellValue() 可能会抛出异常,如果单元格类型不是字符串
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();
}
}
}
读取 .xlsx 文件 (XSSFWorkbook)
.xlsx 是现代的 Excel 格式,使用 XSSFWorkbook 类处理,代码结构与 .xls 非常相似。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadXlsxExample {
public static void main(String[] args) {
String filePath = "path/to/your/data.xlsx";
try (FileInputStream fis = new FileInputStream(filePath);
// 注意:这里使用 XSSFWorkbook
Workbook workbook = new XSSFWorkbook(fis)) {
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
for (Cell cell : row) {
// 获取单元格值的逻辑与 .xls 完全相同
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();
}
}
}
统一 API 读取任意格式 (XSSF/HSSF)
为了代码的通用性,我们最好写一个方法,能自动识别文件格式并使用相应的 API 读取,POI 提供了 WorkbookFactory 工厂类来实现这一点。
这是最推荐的方式,因为它让你的代码更加健壮和灵活。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelUnifiedExample {
public static void main(String[] args) {
String filePath = "path/to/your/data.xlsx"; // 也可以是 .xls
readExcel(filePath);
}
public static void readExcel(String filePath) {
try (FileInputStream fis = new FileInputStream(filePath);
// 使用 WorkbookFactory 自动创建 HSSFWorkbook 或 XSSFWorkbook
Workbook workbook = WorkbookFactory.create(fis)) {
System.out.println("工作表数量: " + workbook.getNumberOfSheets());
Sheet sheet = workbook.getSheetAt(0);
// 使用迭代器遍历,更安全
for (Row row : sheet) {
for (Cell cell : row) {
// 从 POI 3.15 开始,可以使用 getCellType() 方法,它返回枚举类型
CellType cellType = cell.getCellType();
if (cellType == CellType.STRING) {
System.out.print(cell.getStringCellValue() + "\t");
} else if (cellType == CellType.NUMERIC) {
if (DateUtil.isCellDateFormatted(cell)) {
System.out.print(cell.getDateCellValue() + "\t");
} else {
System.out.print(cell.getNumericCellValue() + "\t");
}
} else if (cellType == CellType.BOOLEAN) {
System.out.print(cell.getBooleanCellValue() + "\t");
} else if (cellType == CellType.FORMULA) {
System.out.print(cell.getCellFormula() + "\t");
}
// 其他类型如 BLANK, ERROR 等
}
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
进阶:处理不同数据类型的单元格
在读取单元格时,直接调用 getStringCellValue() 等方法可能会因为类型不匹配而抛出 IllegalStateException。始终使用 getCellType() 进行判断 是最佳实践。
POI 5.x 版本引入了新的 CellType 枚举,推荐使用它来替代旧的常量。
枚举值 (CellType) |
说明 | 获取值的方法 |
|---|---|---|
STRING |
字符串 | cell.getStringCellValue() |
NUMERIC |
数字/日期 | cell.getNumericCellValue() / cell.getDateCellValue() |
BOOLEAN |
布尔值 | cell.getBooleanCellValue() |
FORMULA |
公式 | cell.getCellFormula() / cell.getCachedFormulaResultType() |
BLANK |
空单元格 | - |
ERROR |
错误 | - |
处理数字格式化(显示的值 vs. 实际值)
有时,单元格中存的是数字,但被格式化为显示为百分比或货币。getNumericCellValue() 返回的是实际的数字值,如果你想获取格式化后的字符串,可以使用 DataFormatter。
import org.apache.poi.ss.usermodel.DataFormatter;
// ...
DataFormatter dataFormatter = new DataFormatter();
for (Row row : sheet) {
for (Cell cell : row) {
// dataFormatter.formatCellValue() 会返回单元格显示的值
// 如果是数字,它会返回格式化后的字符串(如 "50%")
// 如果是字符串,它会返回字符串本身
// 如果是公式,它会返回公式的计算结果
System.out.print(dataFormatter.formatCellValue(cell) + "\t");
}
System.out.println();
}
// ...
进阶:读取大文件(SAX 模式,避免内存溢出)
HSSFWorkbook 和 XSSFWorkbook 都会将整个 Excel 文件加载到内存中,如果文件非常大(例如几百 MB),很容易导致 OutOfMemoryError。
对于 .xlsx 文件,POI 提供了 SAX (Event API) 模式,它以流的方式读取文件,逐行解析,内存占用非常小。
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.File;
import java.io.InputStream;
public class ReadLargeXlsxExample {
public static void main(String[] args) throws Exception {
String filePath = "path/to/your/large_data.xlsx";
processOneSheet(filePath, 0); // 读取第一个工作表
}
public static void processOneSheet(String filename, int sheetId) throws Exception {
File xlsxFile = new File(filename);
// 1. 创建 XSSFReader
XSSFReader xssfReader = new XSSFReader(xssfFile);
// 2. 获取共享字符串表
SharedStringsTable sst = xssfReader.getSharedStringsTable();
// 3. 获取 XMLReader
XMLReader parser = XMLReaderFactory.createXMLReader();
// 4. 设置内容处理器
ContentHandler handler = new SheetHandler(sst);
parser.setContentHandler(handler);
// 5. 获取指定工作表的 InputStream
InputStream sheet = xssfReader.getSheet("rId" + (sheetId + 1)); // rId1 是第一个 sheet
InputSource sheetSource = new InputSource(sheet);
// 6. 开始解析
parser.parse(sheetSource);
}
// 自定义的 SheetHandler,用于处理 XML 事件
// 这是一个简化的示例,实际实现会更复杂
private static class SheetHandler implements ContentHandler {
private SharedStringsTable sst;
private String lastContents;
private boolean nextIsString;
public SheetHandler(SharedStringsTable sst) {
this.sst = sst;
}
@Override
public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes) {
if (qName.equals("c")) { // <c> 单元格元素
String cellType = attributes.getValue("t");
if (cellType != null && cellType.equals("s")) {
nextIsString = true; // 如果类型是 "s",说明内容是共享字符串表的索引
} else {
nextIsString = false;
}
}
// 清空 lastContents
lastContents = "";
}
@Override
public void characters(char[] ch, int start, int length) {
lastContents += new String(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) {
if (qName.equals("v")) { // <v> 是单元格的值
if (nextIsString) {
int idx = Integer.parseInt(lastContents);
System.out.print(sst.getItemAt(idx).getString() + "\t");
} else {
System.out.print(lastContents + "\t");
}
} else if (qName.equals("row")) { // 一行结束
System.out.println();
}
}
}
}
注意:SAX 模式比用户模式复杂得多,因为它直接操作底层的 XML,你需要自己处理 XML 事件来构建行和单元格,如果你的文件不是特别大(小于 100MB),建议还是使用
WorkbookFactory的用户模式,因为它简单得多。
完整示例代码
这是一个结合了上述所有优点的完整示例:使用 WorkbookFactory、DataFormatter 和 CellType 枚举。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class CompletePoiReadExample {
public static void main(String[] args) {
// 请替换为你的 Excel 文件路径
String filePath = "C:\\data\\employees.xlsx";
File excelFile = new File(filePath);
if (!excelFile.exists()) {
System.out.println("文件不存在: " + filePath);
return;
}
try (FileInputStream fis = new FileInputStream(excelFile);
Workbook workbook = WorkbookFactory.create(fis)) {
System.out.println("成功加载工作簿: " + workbook.getSheetName(0));
// 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 创建 DataFormatter,用于获取单元格的显示值
DataFormatter dataFormatter = new DataFormatter();
// 遍历每一行
for (Row row : sheet) {
// 遍历每一个单元格
for (Cell cell : row) {
// 打印单元格类型和值
System.out.print("[" + row.getRowNum() + "," + cell.getColumnIndex() + "] ");
System.out.print("类型: " + cell.getCellType() + ", 值: ");
// 使用 DataFormatter 获取格式化后的值
System.out.print(dataFormatter.formatCellValue(cell) + "\t");
}
System.out.println(); // 每行结束后换行
}
} catch (IOException | InvalidFormatException e) {
System.err.println("读取 Excel 文件时出错!");
e.printStackTrace();
}
}
}
总结与最佳实践
- 首选
WorkbookFactory:使用WorkbookFactory.create()来创建Workbook对象,它能自动处理.xls和.xlsx格式,让你的代码更具通用性。 - 使用
try-with-resources:确保所有InputStream和Workbook对象在使用后都能被正确关闭,防止资源泄漏。 - 始终检查单元格类型:在读取单元格值之前,使用
cell.getCellType()进行判断,避免因类型不匹配而抛出异常。 - 利用
DataFormatter:当你需要获取单元格格式化后的显示值(特别是数字和日期)时,DataFormatter是最简单、最可靠的选择。 - 注意大文件处理:对于非常大的
.xlsx文件,考虑使用 SAX 模式(XSSFReader)来避免内存溢出,但对于大多数常规大小的文件,用户模式已经足够。 - 处理异常:妥善处理
IOException和InvalidFormatException,确保程序的健壮性。
希望这份详细的教程能帮助你掌握 Java POI 读取 Excel 的技能!
