Apache POI 从入门到实战:Excel 与 Word 操作攻略
-
- [第一部分:Apache POI 概述与环境搭建](#第一部分:Apache POI 概述与环境搭建)
-
- [1.1 什么是 POI?](#1.1 什么是 POI?)
- [1.2 主要功能](#1.2 主要功能)
- [1.3 POI 的发展历史](#1.3 POI 的发展历史)
- [1.4 POI 的核心组件](#1.4 POI 的核心组件)
- [1.5 环境搭建-添加 Maven 依赖](#1.5 环境搭建-添加 Maven 依赖)
- [第二部分:Excel 操作基础(XSSF)](#第二部分:Excel 操作基础(XSSF))
-
- [2.1 核心概念:Workbook、Sheet、Row、Cell](#2.1 核心概念:Workbook、Sheet、Row、Cell)
-
- [2.1.1 在 POI 代码中的对应关系](#2.1.1 在 POI 代码中的对应关系)
- [2.1.2 它们之间的关系](#2.1.2 它们之间的关系)
- [2.2 第一个程序:创建一个简单的 Excel 文件](#2.2 第一个程序:创建一个简单的 Excel 文件)
-
- [2.2.1 写入 .xlsx 文件(XSSF)](#2.2.1 写入 .xlsx 文件(XSSF))
- [2.2.2 代码解释](#2.2.2 代码解释)
- [2.2.3 运行结果](#2.2.3 运行结果)
- [2.3 添加数据验证(下拉列表)](#2.3 添加数据验证(下拉列表))
- [2.4 读取一个已有的 Excel 文件](#2.4 读取一个已有的 Excel 文件)
-
- [2.4.1 代码解释](#2.4.1 代码解释)
- [2.5 注意事项](#2.5 注意事项)
- [第三部分:Word 操作基础(XWPF)](#第三部分:Word 操作基础(XWPF))
-
- [3.1 核心类:从 `XSSF` 到 `XWPF` 的映射](#3.1 核心类:从
XSSF到XWPF的映射) - [3.2 基本操作:创建、读取、写入](#3.2 基本操作:创建、读取、写入)
-
- [3.2.1 环境搭建](#3.2.1 环境搭建)
- [3.2.2 创建一个简单的文档并写入内容](#3.2.2 创建一个简单的文档并写入内容)
- [3.2.3 读取并打印文档内容](#3.2.3 读取并打印文档内容)
- [3.3 进阶实战:动态模板填充(核心需求)](#3.3 进阶实战:动态模板填充(核心需求))
- [3.4 处理更复杂的元素:图片](#3.4 处理更复杂的元素:图片)
- [3.1 核心类:从 `XSSF` 到 `XWPF` 的映射](#3.1 核心类:从
第一部分:Apache POI 概述与环境搭建
1.1 什么是 POI?
Apache POI 是一个开源的 Java 库,由 Apache 软件基金会维护,用于读写 Microsoft Office 格式的文档。
1.2 主要功能
- Excel :创建、读取、修改、格式化 Excel 文件(
.xls和.xlsx)。 - Word :生成和解析 Word 文档(
.doc和.docx)。 - PowerPoint :操作演示文稿(
.ppt和.pptx)。 - Outlook :处理
.msg邮件文件。
在 Java 后端开发中,最常用的就是 Excel 处理,例如报表生成、数据导入导出、自动化报告等。
1.3 POI 的发展历史
- 最初只支持 OLE 2 格式(即旧版 Office 的
.xls、.doc等)。 - 随着 Office 2007 推出基于 XML 的 OpenXML 格式(
.xlsx、.docx),POI 增加了对 OpenXML 的支持,提供了 XSSF(XML SpreadSheet Format)组件。 - 现在 POI 同时支持两种格式,并且不断优化性能和内存占用。
1.4 POI 的核心组件
POI 按照 Office 文档类型和格式划分了多个组件,其中 Excel 处理是我们学习的重点。
| 组件 | 作用 | 支持格式 |
|---|---|---|
| HSSF (Horrible SpreadSheet Format) | 读写 Excel 97-2003 版本 | .xls |
| XSSF (XML SpreadSheet Format) | 读写 Excel 2007+ 版本 | .xlsx |
| SXSSF (Streaming XSSF) | XSSF 的流式版本,用于处理超大数据量写入,内存占用低 | .xlsx |
| HWPF / XWPF | 读写 Word 文档 | .doc / .docx |
| HSLF / XSLF | 读写 PowerPoint 演示文稿 | .ppt / .pptx |
注意:HSSF 和 XSSF 的 API 非常相似,大部分情况下只需更改 Workbook 的类型即可切换格式。SXSSF 则专门为大数据导出设计,不能读取已有文件。
1.5 环境搭建-添加 Maven 依赖
在 pom.xml 中添加以下依赖:
xml
<dependencies>
<!-- POI 核心库,包含操作 Office 文档的基本类 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.3.0</version>
</dependency>
<!-- POI OOXML 库,用于处理 .xlsx 等基于 XML 的格式 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.3.0</version>
</dependency>
</dependencies>
第二部分:Excel 操作基础(XSSF)
2.1 核心概念:Workbook、Sheet、Row、Cell
在动手编写第一个程序之前,我们有必要先理解 POI 中的四个核心概念,它们正好对应了 Excel 文件的结构层次。可以用一本书的比喻来帮助理解:
- Workbook(工作簿) 就像一本书,是整个 Excel 文件。一个 Excel 文件就是一个工作簿,它可以包含多张工作表。
- Sheet(工作表) 就像书中的每一页,是工作簿中的一个页面。在 Excel 底部可以看到
Sheet1、Sheet2等标签,每个标签对应一个 Sheet。 - Row(行) 就像书页中的一行文字,是 Sheet 中的一行数据。在 Excel 中,行号用数字表示(1,2,3...)。
- Cell(单元格) 就像行中的一个字或词,是行与列交叉的那个小格子。在 Excel 中,单元格用列字母和行号标识(如 A1、B2)。
2.1.1 在 POI 代码中的对应关系
当你用 POI 操作 Excel 时,会使用以下接口/类:
-
Workbook代表整个 Excel 文档。可以是
HSSFWorkbook(处理.xls)或XSSFWorkbook(处理.xlsx)。通过它你可以创建、保存、读取文件。javaWorkbook workbook = new XSSFWorkbook(); // 创建一个新的 .xlsx 工作簿 -
Sheet代表一个工作表,通过
Workbook创建或获取。javaSheet sheet = workbook.createSheet("员工信息"); // 创建一个名为"员工信息"的工作表 -
Row代表 Sheet 中的一行,通过
Sheet创建。javaRow row = sheet.createRow(0); // 创建第 1 行(索引从 0 开始) -
Cell代表 Row 中的一个单元格,通过
Row创建。javaCell cell = row.createCell(0); // 创建该行的第 1 个单元格 cell.setCellValue("Hello POI");
2.1.2 它们之间的关系
text
Workbook (整个Excel文件)
│
└── Sheet1
│ ├── Row 0
│ │ ├── Cell 0
│ │ ├── Cell 1
│ │ └── ...
│ ├── Row 1
│ │ ├── Cell 0
│ │ └── ...
│ └── ...
└── Sheet2
└── ...
在后续的代码示例中,你将看到这些概念的具体运用。
2.2 第一个程序:创建一个简单的 Excel 文件
我们将使用 POI 创建一个包含标题和数据的 Excel 文件,并写入磁盘。
2.2.1 写入 .xlsx 文件(XSSF)
java
@Test
void excel() {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
// 创建xlsx表格文件
XSSFSheet sheet = workbook.createSheet("题目信息");
XSSFRow headRow = sheet.createRow(0);
List<String> list = List.of("序号", "题目", "题目选项-A", "题目选项-B", "题目选项-C", "题目选项-D", "正确选项");
for (int i = 0; i < list.size(); i++) {
headRow.createCell(i).setCellValue(list.get(i));
}
List<String> data = List.of("1", "1+1=?", "1", "2", "3", "4", "B");
XSSFRow row = sheet.createRow(1);
for (int i = 0; i < data.size(); i++) {
row.createCell(i).setCellValue(data.get(i));
}
try (FileOutputStream stream = new FileOutputStream("题目导入模板.xlsx")) {
workbook.write(stream);
}
} catch (Exception e) {
e.printStackTrace();
}
}
2.2.2 代码解释
- Workbook :代表整个 Excel 文件。XSSFWorkbook 对应
.xlsx,若想生成旧版.xls,可替换为HSSFWorkbook。 - Sheet:工作表,可以多个。
- Row:行,索引从 0 开始。
- Cell :单元格,通过
createCell()创建,然后使用setCellValue()写入不同类型的值(int、String、double、LocalDate 等)。 - write():将内存中的 Workbook 写入输出流。
- 资源管理 :使用 try-with-resources 自动关闭
Workbook和FileOutputStream,避免内存泄漏。
2.2.3 运行结果
运行程序后,项目根目录下会生成一个名为"题目导入模板.xlsx"的文件,打开后应包含以下内容:
| 序号 | 题目 | 题目选项-A | 题目选项-B | 题目选项-C | 题目选项-D | 正确选项 |
|---|---|---|---|---|---|---|
| 1 | 1+1=? | 1 | 2 | 3 | 4 | B |
2.3 添加数据验证(下拉列表)
为某一列添加下拉选项,例如"正确选项"列只能选择 A/B/C/D。
java
@Test
void excel2() {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
// 创建xlsx表格文件
XSSFSheet sheet = workbook.createSheet("题目信息");
XSSFRow headRow = sheet.createRow(0);
List<String> list = List.of("序号", "题目", "题目选项-A", "题目选项-B", "题目选项-C", "题目选项-D", "正确选项");
for (int i = 0; i < list.size(); i++) {
headRow.createCell(i).setCellValue(list.get(i));
}
List<String> data = List.of("1", "1+1=?", "1", "2", "3", "4", "B");
XSSFRow row = sheet.createRow(1);
for (int i = 0; i < data.size(); i++) {
row.createCell(i).setCellValue(data.get(i));
}
// 定义下拉列表的值
String[] options = {"A", "B", "C", "D"};
// 创建数据验证帮助类
DataValidationHelper helper = sheet.getDataValidationHelper();
// 创建数据验证约束(显式值列表)
DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
// 设置应用范围(例如从第2行第3列开始,到第10行第3列结束)
CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, 6, 6); // 行从1到9,列索引2(即C列)
// 创建数据验证对象
DataValidation validation = helper.createValidation(constraint, addressList);
validation.setShowErrorBox(true); // 输入非法时显示错误框
validation.setSuppressDropDownArrow(true); // 显示下拉箭头
validation.setShowPromptBox(true); // 显示提示框
validation.createPromptBox("请选择", "从 A、B、C、D 中选择正确答案");
// 添加到 sheet
sheet.addValidationData(validation);
try (FileOutputStream stream = new FileOutputStream("题目导入模板.xlsx")) {
workbook.write(stream);
}
} catch (Exception e) {
e.printStackTrace();
}
}
2.4 读取一个已有的 Excel 文件
接下来,我们尝试读取刚才生成的文件,并输出其中的数据。
java
@Test
void read_excel() {
try (FileInputStream fileInputStream = new FileInputStream("题目导入模板.xlsx")){
Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
//循环行
for (Row row : sheet) {
//循环列
for (Cell cell : row) {
// 需要判断单元格类型
switch (cell.getCellType()) {
case STRING:
String cellValue = cell.getStringCellValue();
System.out.println(cellValue);
break;
case NUMERIC:
System.out.println(cell.getNumericCellValue());
break;
case BOOLEAN:
System.out.println(cell.getBooleanCellValue());
break;
case FORMULA:
System.out.println(cell.getCellFormula());
break;
}
}
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
2.4.1 代码解释
- 读取文件 :通过
FileInputStream打开文件,然后构造XSSFWorkbook。 - 获取 Sheet :可以用
getSheetAt(0)获取第一个工作表,或getSheet("员工信息")按名称获取。 - 遍历行和单元格 :使用增强 for 循环遍历
sheet和row非常方便。 - 单元格类型判断 :
cell.getCellType()返回枚举CellType。- 对于数值类型,还需要判断是否是日期(
DateUtil.isCellDateFormatted(cell)),因为 Excel 中的日期也是数值存储的。
- 数据输出:根据类型调用对应的 getter 方法。
2.5 注意事项
-
使用
HSSFWorkbook和.xls文件时,最大行数限制为 65536 行,列数 256 列。 -
XSSFWorkbook支持更多行(1048576)和列(16384),且文件是 XML 压缩包,体积更小。 -
如果你的数据量非常大(例如超过 10 万行),建议学习后续的 SXSSF,它不会将所有数据保留在内存中,而是写入临时文件。
-
Workbook和文件流必须关闭,否则可能导致文件被占用或内存泄漏。务必使用 try-with-resources 或在 finally 块中关闭。 -
频繁调用
createCell和setCellValue是正常的,但如果写入大量数据,可以考虑使用SXSSF和分批写入。 -
避免在循环中创建不必要的样式对象,样式应复用。
-
OutOfMemoryError:写入超大数据时出现,应考虑使用 SXSSF。
-
InvalidFormatException :文件格式不匹配,例如用
XSSFWorkbook读取.xls文件。 -
NullPointerException:未正确初始化单元格或行。
第三部分:Word 操作基础(XWPF)
既然你已经熟练掌握了 POI 操作 Excel(.xlsx)的方法,那么学习操作 Word(.docx)会非常轻松。POI 操作 .docx 的核心思想与操作 Excel 高度一致,只是将 XSSFWorkbook(工作簿)换成了 XWPFDocument(文档)。
下面我们基于你已有的知识,快速掌握 XWPF 的使用。
3.1 核心类:从 XSSF 到 XWPF 的映射
你可以通过这个对比表格,瞬间理解 .docx 操作的核心类:
| Excel 概念 (你已掌握) | Word 概念 (你将学习) | 对应 POI 类 |
|---|---|---|
| XSSFWorkbook (整个Excel文件) | XWPFDocument (整个Word文档) | XWPFDocument |
| XSSFSheet (工作表) | 无直接对应 (Word文档直接包含段落) | (文档本身就是内容容器) |
| XSSFRow (行) | XWPFParagraph (段落) | XWPFParagraph |
| XSSFCell (单元格) | XWPFRun (具有相同格式的文本片段) | XWPFRun |
| XSSFTable (Excel表格) | XWPFTable (Word表格) | XWPFTable |
最重要的概念转变 :在 Excel 中,你操作的是行和单元格 ;在 Word 中,你主要操作的是段落和文本片段(Run) 。一个段落(XWPFParagraph)可以包含多个 Run,每个 Run 可以有自己的格式(字体、颜色、大小等)。
3.2 基本操作:创建、读取、写入
就像你之前用 XSSFWorkbook 一样,我们用 XWPFDocument 来操作 Word 文档。
3.2.1 环境搭建
确保你的 pom.xml 中已包含 POI 依赖:
xml
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-oozml</artifactId> <!-- 注意是 ooxml,包含操作 .docx 的类 -->
<version>5.3.0</version>
</dependency>
3.2.2 创建一个简单的文档并写入内容
这就像你创建 Excel 文件时,先建 Workbook,再建 Sheet,再建 Row 一样。
java
import org.apache.poi.xwpf.usermodel.*;
// 1. 创建文档 (相当于 XSSFWorkbook)
try (XWPFDocument document = new XWPFDocument()) {
// 2. 创建一个段落 (相当于在文档中新增一行)
XWPFParagraph paragraph = document.createParagraph();
// 设置段落对齐方式
paragraph.setAlignment(ParagraphAlignment.CENTER);
// 3. 创建一个文本片段 (相当于给单元格赋值)
XWPFRun run = paragraph.createRun();
run.setText("Hello, World! This is my first Word document.");
run.setBold(true); // 设置粗体
run.setFontSize(16); // 设置字号
run.setColor("FF0000"); // 设置颜色 (红色)
// 4. 再创建一个普通段落
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setText("This is a normal paragraph without any formatting.");
// 5. 保存文档 (和保存 Excel 一样)
try (FileOutputStream out = new FileOutputStream("MyFirstWord.docx")) {
document.write(out);
}
System.out.println("文档创建成功!");
}
这段代码的逻辑和你创建 Excel 时如出一辙,只是将 sheet.createRow() 换成了 document.createParagraph() 。
3.2.3 读取并打印文档内容
读取 .docx 文件,就像你之前用 XSSFWorkbook 读取 Excel 一样。
java
// 1. 加载文档
try (FileInputStream fis = new FileInputStream("MyFirstWord.docx");
XWPFDocument document = new XWPFDocument(fis)) {
// 2. 获取所有段落并打印
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph para : paragraphs) {
// getText() 方法可以获取段落中的所有文本
System.out.println(para.getText());
}
} catch (IOException e) {
e.printStackTrace();
}
document.getParagraphs() 就像 workbook.getSheetAt(0).rowIterator(),让你遍历文档中的每一个段落 。
3.3 进阶实战:动态模板填充(核心需求)
在实际开发中,我们经常需要基于一个预设好格式的 Word 模板,动态地填充数据(如生成合同、报告)。这就是你接下来要掌握的重点。
思路 :在模板文件中使用占位符(如 {``{name}}),然后通过 POI 读取模板,找到这些占位符并用真实数据替换 。
示例代码 :假设模板文件 template.docx 中包含一段话:尊敬的 {``{name}},您的订单号是 {``{orderId}}。
java
import org.apache.poi.xwpf.usermodel.*;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class WordTemplateDemo {
public static void main(String[] args) {
// 准备要替换的数据
Map<String, String> dataMap = new HashMap<>();
dataMap.put("{{name}}", "张三");
dataMap.put("{{orderId}}", "NO20231027001");
try (FileInputStream fis = new FileInputStream("template.docx");
XWPFDocument document = new XWPFDocument(fis);
FileOutputStream out = new FileOutputStream("generated.docx")) {
// 遍历所有段落进行替换
for (XWPFParagraph paragraph : document.getParagraphs()) {
replaceInParagraph(paragraph, dataMap);
}
// 保存替换后的文档
document.write(out);
System.out.println("文档生成成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 在单个段落中替换占位符
*/
private static void replaceInParagraph(XWPFParagraph paragraph, Map<String, String> dataMap) {
String paragraphText = paragraph.getText();
// 检查该段落是否包含任何需要替换的占位符
for (Map.Entry<String, String> entry : dataMap.entrySet()) {
if (paragraphText.contains(entry.getKey())) {
// 关键:替换整个段落的内容,但尽量保持格式
// 简单起见,我们获取第一个 Run 并设置新文本(这会丢失原段落中不同部分的格式)
// 更精细的做法是遍历 Runs 进行部分替换,但实现较复杂。
// 这里演示一种通用且常用的简单方法:
if (paragraph.getRuns().size() > 0) {
XWPFRun run = paragraph.getRuns().get(0);
String newText = paragraphText.replace(entry.getKey(), entry.getValue());
run.setText(newText, 0); // 设置新文本,并从索引0开始覆盖
}
break; // 替换后跳出循环
}
}
}
}
代码解释:
- 加载模板 :像读取普通文件一样加载
template.docx。 - 准备数据 :用一个
Map存储占位符和真实值的对应关系。 - 遍历替换 :核心在于
replaceInParagraph方法。它检查每个段落的文本是否包含占位符,如果包含,则获取该段落的第一个Run(文本片段)并用替换后的完整文本覆盖 。- 注意 :上述替换方式会丢失原段落中除第一个
Run外的格式。对于复杂的模板,可能需要更精细地遍历和操作每一个Run,但理解这个基础版本是第一步。
- 注意 :上述替换方式会丢失原段落中除第一个
3.4 处理更复杂的元素:图片
就像 Excel 中可以操作表格一样,Word 中也能操作表格 。
-
插入图片 :
javaXWPFParagraph paragraph = document.createParagraph(); XWPFRun run = paragraph.createRun(); // 添加图片,需要指定图片流、图片类型、文件名、宽度、高度 try (FileInputStream picData = new FileInputStream("logo.png")) { run.addPicture(picData, Document.PICTURE_TYPE_PNG, "logo.png", Units.toEMU(100), Units.toEMU(100)); }Units.toEMU()用于将像素等常用单位转换为 Word 内部使用的 EMU 单位 。