Apache POI 是一个开源的 Java 库,专门用于处理 Microsoft Office 格式的文件,包括 Word、Excel 和 PowerPoint,对于 Excel,POI 提供了两种主要的 API:

- HSSF (Horrible SpreadSheet Format): 用于处理
.xls格式的 Excel 文件(Excel 97-2003)。 - XSSF (XML SpreadSheet Format): 用于处理
.xlsx格式的 Excel 文件(Excel 2007 及以上版本)。 - SXSSF (Streaming Usermodel API): XSSF 的一种流式实现,用于处理非常大的
.xlsx文件,以减少内存消耗。
下面我将分步介绍如何使用这些 API,并提供完整的代码示例。
第一步:添加 Maven 依赖
在你的 pom.xml 文件中添加 POI 的依赖,为了同时支持 .xls 和 .xlsx,以及一些其他功能(如日期处理),我们通常推荐使用 poi-ooxml 这个完整的依赖包。
<dependencies>
<!-- Apache 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>
<!-- 用于处理 .xlsx 格式中的 OOXML 组件(如图片) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
第二步:准备一个 Excel 文件
假设我们有一个名为 data.xlsx 的文件,内容如下:
| 姓名 | 年龄 | 生日 | 是否在职 | 薪资 |
|---|---|---|---|---|
| 张三 | 28 | 1995-05-20 | true | 50 |
| 李四 | 35 | 1988-10-01 | false | 00 |
| 王五 | 22 | 2001-08-15 | true | 75 |
这个文件是 .xlsx 格式,如果是 .xls 格式,代码逻辑基本相同,只需要把 XSSFWorkbook 换成 HSSFWorkbook。

第三步:编写 Java 代码解析 Excel
我们将分两种主要方式来解析:
- 事件模型 (SAX API):适用于读取非常大的文件,内存占用小,它不将整个文件加载到内存,而是逐行触发事件,由开发者处理。
- 用户模型 (Usermodel API):最常用、最简单的方式,它会将整个 Excel 文件加载到内存中,形成一个可以操作的树形结构,对于中小型文件非常方便。
用户模型 - 推荐(简单易用)
这是最直观的方式,类似于操作一个二维数组。
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 ExcelReaderUserModel {
public static void main(String[] args) {
// 1. 定义文件路径
String filePath = "path/to/your/data.xlsx"; // 请替换为你的文件路径
try (FileInputStream fis = new FileInputStream(filePath);
// 2. 根据文件格式创建 Workbook 对象
// 对于 .xls 文件,使用 new HSSFWorkbook(fis);
Workbook workbook = new XSSFWorkbook(fis)) {
// 3. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 4. 遍历每一行(从0开始,0通常是标题行)
for (Row row : sheet) {
// 跳过标题行(可选)
if (row.getRowNum() == 0) {
continue;
}
// 5. 遍历每一列
// 假设列顺序是:姓名, 年龄, 生日, 是否在职, 薪资
String name = getCellValueAsString(row.getCell(0));
int age = (int) row.getCell(1).getNumericCellValue();
Date birthday = row.getCell(2).getDateCellValue();
boolean isActive = row.getCell(3).getBooleanCellValue();
double salary = row.getCell(4).getNumericCellValue();
// 格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String formattedBirthday = sdf.format(birthday);
// 6. 打印数据
System.out.printf("姓名: %s, 年龄: %d, 生日: %s, 是否在职: %b, 薪资: %.2f%n",
name, age, formattedBirthday, isActive, salary);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将 Cell 的值安全地转换为字符串
* @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)) {
return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
} else {
// 处理普通数字,避免科学计数法
return String.valueOf((long) cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 获取公式的计算结果
return String.valueOf(cell.getNumericCellValue());
case BLANK:
return "";
default:
return "";
}
}
}
代码解析:
FileInputStream: 用于读取 Excel 文件。Workbook: 代表整个 Excel 文档。.xlsx用XSSFWorkbook,.xls用HSSFWorkbook,使用try-with-resources确保Workbook和FileInputStream在使用后被自动关闭。Sheet: 代表 Excel 中的一个工作表(Sheet)。getSheetAt(0)获取第一个工作表。Row: 代表工作表中的一行,我们使用增强的 for 循环遍历每一行。Cell: 代表行中的一个单元格,同样使用 for 循环遍历每一列。cell.getCellType(): 非常重要!Excel 单元格有多种类型(字符串、数字、布尔值、公式等),必须根据类型调用不同的getXXXCellValue()方法。CellType.STRINGCellType.NUMERICCellType.BOOLEANCellType.FORMULACellType.BLANK
DateUtil.isCellDateFormatted(cell): 一个工具方法,用于判断数字格式的单元格是否代表日期。getCellValueAsString辅助方法: 这是一个很好的实践,可以统一处理各种单元格类型,避免在业务逻辑中写大量的switch语句,并处理了null值。
事件模型 - 适用于大文件
事件模型更复杂一些,但它不将整个文件加载到内存,因此可以处理非常大的文件而不会导致 OutOfMemoryError。
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.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class ExcelReaderEventModel {
public static void main(String[] args) {
String filePath = "path/to/your/data.xlsx"; // 请替换为你的文件路径
try {
processOneSheet(filePath);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void processOneSheet(String filePath) throws IOException, InvalidFormatException, SAXException {
try (InputStream is = new FileInputStream(filePath)) {
// 1. 创建 XSSFReader
XSSFReader reader = new XSSFReader(is);
// 2. 获取共享字符串表
SharedStringsTable sst = reader.getSharedStringsTable();
// 3. 创建 XMLReader
XMLReader parser = XMLReaderFactory.createXMLReader();
// 4. 设置自定义的 ContentHandler
// MyHandler 是我们自己实现的处理器
ContentHandler handler = new MyHandler(sst);
parser.setContentHandler(handler);
// 5. 获取第一个工作表的 InputStream 并解析
InputStream sheetStream = reader.getSheet("rId1"); // "rId1" 通常是第一个sheet
InputSource sheetSource = new InputSource(sheetStream);
parser.parse(sheetSource);
sheetStream.close();
// 从处理器中获取解析结果
List<String[]> dataList = ((MyHandler) handler).getDataList();
for (String[] row : dataList) {
for (String cell : row) {
System.out.print(cell + "\t");
}
System.out.println();
}
}
}
}
/**
* 自定义的 SAX ContentHandler,用于处理 Excel 的 XML 数据
*/
class MyHandler extends DefaultHandler {
private SharedStringsTable sst;
private String[] currentRowData;
private int currentCellIndex = 0;
private StringBuilder currentCellValue;
private List<String[]> dataList = new ArrayList<>();
private boolean isCellValue = false;
public MyHandler(SharedStringsTable sst) {
this.sst = sst;
}
public List<String[]> getDataList() {
return dataList;
}
@Override
public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes) throws SAXException {
if ("row".equals(qName)) {
// 开始新的一行,初始化行数据数组
// 假设每行最多20列,可以根据实际情况调整
currentRowData = new String[20];
currentCellIndex = 0;
} else if ("c".equals(qName)) {
// 遇到单元格,重置当前单元格值
currentCellValue = new StringBuilder();
// 可以在这里根据单元格类型做不同处理,这里简化处理
} else if ("v".equals(qName)) {
// 遇到单元格的值
isCellValue = true;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (isCellValue) {
currentCellValue.append(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("v".equals(qName)) {
isCellValue = false;
String value = currentCellValue.toString();
// 处理共享字符串
if (value != null && !value.isEmpty()) {
try {
int idx = Integer.parseInt(value);
value = sst.getItemAt(idx).getString();
} catch (NumberFormatException e) {
// 如果不是共享字符串索引,直接使用原始值(如数字、布尔值)
// 这里简化处理,实际应用中需要更复杂的逻辑
}
}
// 将值存入当前行数组
if (currentCellIndex < currentRowData.length) {
currentRowData[currentCellIndex++] = value;
}
} else if ("row".equals(qName)) {
// 一行结束,将当前行数据添加到总列表中
// 截取有效数据长度,避免末尾的null
String[] trimmedRow = new String[currentCellIndex];
System.arraycopy(currentRowData, 0, trimmedRow, 0, currentCellIndex);
dataList.add(trimmedRow);
}
}
}
代码解析:
- XSSFReader: 用于读取
.xlsx文件的底层 XML 结构。 - SharedStringsTable (SST): Excel 为了节省空间,会把所有字符串(如列名、文本内容)都存放在一个公共的字符串池里,单元格中存的是这个池中的索引,我们需要通过 SST 来获取真正的字符串值。
- XMLReader 和 ContentHandler: 这是标准的 SAX 解析模型。
MyHandler继承DefaultHandler,我们在这里重写startElement,characters,endElement方法来解析 XML 流。startElement: 当遇到 XML 标签开始时触发,我们在这里识别<row>(行开始)和<c>(单元格开始)。characters: 当遇到标签内的文本内容时触发,这里就是单元格的值。endElement: 当遇到 XML 标签结束时触发,我们在这里处理<v>(单元格值结束),将值从 SST 中取出,并添加到当前行的数组中,当遇到</row>时,将整行数据存入总列表。
reader.getSheet("rId1"): 获取特定工作表的流。"rId1"是一个内部引用 ID,通常对应第一个工作表,要获取所有工作表,需要遍历Workbook的相关部分。
总结与建议
| 特性 | 用户模型 | 事件模型 |
|---|---|---|
| 易用性 | 高,API 直观,像操作对象 | 低,需要理解 SAX 和 XML 结构 |
| 内存占用 | 高,将整个文件加载到内存 | 低,逐行解析,内存占用恒定 |
| 性能 | 中等 | 对于大文件,性能更好 |
| 适用场景 | 中小型 Excel 文件,需要灵活访问任意单元格 | 大型或超大型 Excel 文件(几百MB以上) |
给你的建议:
- 对于 99% 的应用场景,直接使用“用户模型”,它简单、快速、不易出错。
- 只有当你的 Excel 文件特别大(比如超过 100MB),并且你遇到了内存溢出(
OutOfMemoryError)问题时,才考虑切换到“事件模型”,虽然复杂,但它是解决大文件问题的标准方案。
希望这个详细的教程能帮助你掌握 Java POI 解析 Excel!
