目录
- Apache POI 简介
- 环境配置
- 核心概念
- 读取 Excel 文件(基础示例)
- 读取
.xlsx格式 (XSSF) - 读取
.xls格式 (HSSF)
- 读取
- 进阶用法
- 读取不同数据类型(数字、日期、布尔值)
- 获取单元格样式
- 处理公式单元格
- 读取大文件(使用 SAX 模式)
- 完整示例代码
- 最佳实践与注意事项
Apache POI 简介
Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,由 Apache 软件基金会维护,它提供了 API,允许 Java 程序读取和写入 Microsoft Office 格式的文件,最常用的就是 Excel 文件(.xls 和 .xlsx)。

POI 主要处理两种格式的 Excel:
.xls: Excel 97-2003 格式,使用HSSF(Horrible Spreadsheet Format) API。.xlsx: Excel 2007 及以后版本,基于 Office Open XML 格式,使用XSSF(XML Spreadsheet Format) API。
环境配置
你需要在你的项目中添加 POI 的依赖,如果你使用 Maven,在 pom.xml 文件中添加以下依赖:
<!-- 如果只需要读取 .xlsx 文件,可以只添加这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version> <!-- 建议使用最新稳定版 -->
</dependency>
<!-- 如果需要读取旧的 .xls 文件,需要额外添加这个 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</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 文件中添加:
// 如果只需要读取 .xlsx 文件 implementation 'org.apache.poi:poi-ooxml:5.2.5' // 如果需要读取 .xls 文件 implementation 'org.apache.poi:poi:5.2.5'
核心概念
在开始编写代码前,了解 POI 的核心对象模型非常重要:

Workbook: 代表一个 Excel 工作簿,是所有表格的入口。XSSFWorkbook: 对应.xlsx文件。HSSFWorkbook: 对应.xls文件。
Sheet: 代表一个工作表(Excel 底部的标签页,如 Sheet1, Sheet2)。Row: 代表一行,由多个单元格组成。Cell: 代表一个单元格,是数据存储的最小单位。DataFormatter: 一个非常有用的工具类,可以自动将单元格的值格式化为字符串,而无需关心其原始数据类型(数字、日期、布尔值等)。
读取 Excel 文件(基础示例)
下面我们来看如何读取一个 Excel 文件,我们将创建一个通用的方法,根据文件后缀名自动选择使用 HSSF 还是 XSSF。
步骤:
- 获取
Workbook对象。 - 获取指定的
Sheet。 - 遍历
Sheet中的每一行 (Row)。 - 遍历每一行中的每一个单元格 (
Cell)。 - 获取单元格的值。
示例代码片段:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ExcelReader {
public static void main(String[] args) {
// Excel文件路径
String excelFilePath = "C:\\path\\to\\your\\file.xlsx"; // 或者 file.xls
try (FileInputStream fis = new FileInputStream(excelFilePath);
Workbook workbook = WorkbookFactory.create(fis)) { // 使用WorkbookFactory可以自动识别.xls或.xlsx
// 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 遍历每一行
for (Row row : sheet) {
// 遍历每一个单元格
for (Cell cell : row) {
// 打印单元格的值
System.out.print(getCellValueAsString(cell) + "\t");
}
System.out.println(); // 换行
}
} 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();
case NUMERIC:
// 检查是数字还是日期
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
return String.valueOf(cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 对于公式单元格,获取其计算后的值
return cell.getCellFormula();
case BLANK:
return "";
default:
return "";
}
}
}
代码解释:
try-with-resources: 这是 Java 7+ 的推荐写法,可以自动关闭FileInputStream和Workbook,避免资源泄漏。WorkbookFactory.create(fis): 这是一个非常方便的工厂方法,它会根据文件内容自动创建XSSFWorkbook或HSSFWorkbook,你无需手动判断文件后缀。sheet.getSheetAt(0): 获取工作簿中第一个工作表(索引从0开始)。for (Row row : sheet)和for (Cell cell : row): 使用增强 for 循环遍历行和单元格。getCellValueAsString(): 这是一个辅助方法,用于处理不同类型的单元格数据,使其统一输出为字符串。DateUtil.isCellDateFormatted(cell)是判断数字单元格是否为日期格式的关键。
进阶用法
1 读取不同数据类型(推荐使用 DataFormatter)
手动判断单元格类型比较繁琐,POI 提供了 DataFormatter 工具类,可以大大简化代码。
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
// ...
DataFormatter dataFormatter = new DataFormatter();
for (Row row : sheet) {
for (Cell cell : row) {
// getCellValueAsString 会自动处理数字、日期、布尔值等,并返回格式化后的字符串
String cellValue = dataFormatter.formatCellValue(cell);
System.out.print(cellValue + "\t");
}
System.out.println();
}
优点:

- 代码更简洁。
- 能正确应用单元格的自定义格式(如 "¥#,##0.00")。
- 对于公式单元格,它返回的是公式的字符串,而不是计算结果,如果需要计算结果,需要额外处理。
2 获取单元格样式
你可以获取单元格的字体、颜色、对齐方式、边框等样式信息。
CellStyle cellStyle = cell.getCellStyle();
// 获取字体
Font font = workbook.getFontAt(cellStyle.getFontIndex());
System.out.println("字体名称: " + font.getFontName());
System.out.println("字体大小: " + font.getFontHeightInPoints());
System.out.println("是否加粗: " + font.getBold());
// 获取背景颜色
// 注意:XSSF 和 HSSF 获取颜色的方式不同
// XSSF
if (workbook instanceof XSSFWorkbook) {
XSSFCellStyle xssfCellStyle = (XSSFCellStyle) cellStyle;
XSSFColor xssfColor = xssfCellStyle.getFillForegroundColorColor();
if (xssfColor != null) {
byte[] rgb = xssfColor.getRGB();
// ...处理rgb颜色
}
}
// HSSF
if (workbook instanceof HSSFWorkbook) {
HSSFCellStyle hssfCellStyle = (HSSFCellStyle) cellStyle;
short index = hssfCellStyle.getFillForegroundColorIndex();
HSSFColor hssfColor = ((HSSFWorkbook) workbook).getCustomPalette().getColor(index);
if (hssfColor != null) {
short[] rgb = hssfColor.getRGB();
// ...处理rgb颜色
}
}
3 处理公式单元格
默认情况下,cell.getCellType() 返回 FORMULA,你需要调用 cell.getCachedFormulaResultType() 来获取公式计算结果的类型,然后再获取值。
if (cell.getCellType() == CellType.FORMULA) {
CellType resultType = cell.getCachedFormulaResultType();
switch (resultType) {
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
System.out.println("公式计算结果(日期): " + cell.getDateCellValue());
} else {
System.out.println("公式计算结果(数字): " + cell.getNumericCellValue());
}
break;
case STRING:
System.out.println("公式计算结果(字符串): " + cell.getStringCellValue());
break;
case BOOLEAN:
System.out.println("公式计算结果(布尔值): " + cell.getBooleanCellValue());
break;
case ERROR:
System.out.println("公式计算结果(错误): " + cell.getErrorCellValue());
break;
}
}
4 读取大文件(SAX 模式)
对于非常大的 Excel 文件(如几百MB),使用 XSSFWorkbook 会将整个文件加载到内存中,容易导致 OutOfMemoryError,这时,应该使用 SAX 模式(Event API)进行流式读取,它逐行解析,内存占用非常小。
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.xml.sax.Attributes;
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.InputStream;
public class LargeExcelReader {
public static void main(String[] args) throws Exception {
String excelFilePath = "C:\\path\\to\\large\\file.xlsx";
try (OPCPackage pkg = OPCPackage.open(excelFilePath)) {
XSSFReader reader = new XSSFReader(pkg);
SharedStringsTable sst = reader.getSharedStringsTable();
XMLReader parser = XMLReaderFactory.createXMLReader();
parser.setContentHandler(new SheetHandler(sst));
InputStream sheet = reader.getSheet("rId1"); // 获取第一个工作表
InputSource source = new InputSource(sheet);
parser.parse(source);
}
}
// 自定义的SAX事件处理器
private static class SheetHandler extends DefaultHandler {
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, Attributes attributes) throws SAXException {
// c => cell
if (qName.equals("c")) {
// 图例: B=布尔值, S=字符串, n=数字
String cellType = attributes.getValue("t");
if (cellType != null && cellType.equals("s")) {
nextIsString = true;
} else {
nextIsString = false;
}
}
// 清空内容
lastContents = "";
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (nextIsString) {
int idx = Integer.parseInt(lastContents);
lastContents = sst.getItemAt(idx).getString();
nextIsString = false;
}
// v => 单元格的值
if (qName.equals("v")) {
System.out.print(lastContents + "\t");
}
// 每行结束,打印换行符
if (qName.equals("row")) {
System.out.println();
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
lastContents += new String(ch, start, length);
}
}
}
SAX 模式要点:
- 它不直接提供
Row和Cell对象,而是通过解析 XML 事件来获取数据。 - 需要自己处理 XML 解析逻辑,相对复杂。
- 对于共享的字符串(如 Excel 中的 "姓名", "年龄"),会先存入一个共享字符串表,单元格中只存索引,需要通过索引去表中查找。
完整示例代码
这是一个完整的、可运行的示例,读取一个包含标题的 Excel 文件,并将数据封装到 Student 对象列表中。
Student.java (POJO)
public class Student {
private String name;
private int age;
private double score;
private boolean isMale;
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public double getScore() { return score; }
public void setScore(double score) { this.score = score; }
public boolean isMale() { return isMale; }
public void setMale(boolean male) { isMale = male; }
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
", isMale=" + isMale +
'}';
}
}
ExcelReaderExample.java
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ExcelReaderExample {
public static void main(String[] args) {
String filePath = "students.xlsx"; // 确保文件和程序在同一目录,或提供完整路径
List<Student> students = readExcel(filePath);
// 打印读取到的数据
for (Student student : students) {
System.out.println(student);
}
}
public static List<Student> readExcel(String filePath) {
List<Student> studentList = new ArrayList<>();
DataFormatter dataFormatter = new DataFormatter();
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = WorkbookFactory.create(fis)) {
Sheet sheet = workbook.getSheetAt(0);
// 假设第一行是标题,从第二行开始读取数据
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue; // 跳过空行
}
Student student = new Student();
// 读取姓名 (String)
Cell nameCell = row.getCell(0);
student.setName(dataFormatter.formatCellValue(nameCell));
// 读取年龄 (Numeric)
Cell ageCell = row.getCell(1);
if (ageCell.getCellType() == CellType.NUMERIC) {
student.setAge((int) ageCell.getNumericCellValue());
}
// 读取分数 (Numeric)
Cell scoreCell = row.getCell(2);
if (scoreCell.getCellType() == CellType.NUMERIC) {
student.setScore(scoreCell.getNumericCellValue());
}
// 读取性别 (Boolean)
Cell maleCell = row.getCell(3);
if (maleCell.getCellType() == CellType.BOOLEAN) {
student.setMale(maleCell.getBooleanCellValue());
}
// 读取出生日期 (Date)
Cell birthDateCell = row.getCell(4);
if (birthDateCell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(birthDateCell)) {
Date birthDate = birthDateCell.getDateCellValue();
// 这里可以处理日期,例如格式化或存储为字符串
System.out.println("读取到日期: " + birthDate);
}
studentList.add(student);
}
} catch (IOException e) {
e.printStackTrace();
}
return studentList;
}
}
students.xlsx 文件内容示例:
| 姓名 | 年龄 | 分数 | 是否男性 | 出生日期 |
| :--- | :--- | :--- | :--- | :--- |
| 张三 | 20 | 89.5 | TRUE | 2003-05-15 |
| 李四 | 21 | 92.0 | FALSE | 2002-11-20 |
| 王五 | 19 | 78.5 | TRUE | 2004-01-30 |
最佳实践与注意事项
- 总是使用
try-with-resources: 确保Workbook和FileInputStream等资源被正确关闭,防止内存泄漏。 - 优先使用
DataFormatter: 对于大多数场景,使用DataFormatter.formatCellValue()是最简单、最不容易出错的方式来获取单元格的显示值。 - 处理空单元格和空行: 在遍历时,始终检查
row == null或cell == null,避免NullPointerException。 - 为大数据量使用 SAX 模型: 当处理超过几十兆的 Excel 文件时,请务必使用
Event API(SAX 模式) 来避免内存溢出。 - 日期处理: Excel 中日期通常存储为数字,使用
DateUtil.isCellDateFormatted(cell)来判断,并用cell.getDateCellValue()来获取Date对象。 - 性能考虑: 反复创建
Workbook对象会很慢,如果你的应用需要频繁读取同一个模板文件,可以考虑缓存Workbook对象(但要小心并发问题)。 - 版本兼容性:
XSSF(.xlsx) 是目前的主流格式,功能更全,性能也更好,除非必须兼容旧版 Office 2003,否则建议优先使用.xlsx格式。
希望这份详细的指南能帮助你熟练地使用 Java POI 读取 Excel 文件!
