Apache POI 从入门到实战:Excel 与 Word操作攻略

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 核心类:从 XSSFXWPF 的映射)
      • [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 处理更复杂的元素:图片)

第一部分: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 底部可以看到 Sheet1Sheet2 等标签,每个标签对应一个 Sheet。
  • Row(行) 就像书页中的一行文字,是 Sheet 中的一行数据。在 Excel 中,行号用数字表示(1,2,3...)。
  • Cell(单元格) 就像行中的一个字或词,是行与列交叉的那个小格子。在 Excel 中,单元格用列字母和行号标识(如 A1、B2)。
2.1.1 在 POI 代码中的对应关系

当你用 POI 操作 Excel 时,会使用以下接口/类:

  • Workbook

    代表整个 Excel 文档。可以是 HSSFWorkbook(处理 .xls)或 XSSFWorkbook(处理 .xlsx)。通过它你可以创建、保存、读取文件。

    java 复制代码
    Workbook workbook = new XSSFWorkbook(); // 创建一个新的 .xlsx 工作簿
  • Sheet

    代表一个工作表,通过 Workbook 创建或获取。

    java 复制代码
    Sheet sheet = workbook.createSheet("员工信息"); // 创建一个名为"员工信息"的工作表
  • Row

    代表 Sheet 中的一行,通过 Sheet 创建。

    java 复制代码
    Row row = sheet.createRow(0); // 创建第 1 行(索引从 0 开始)
  • Cell

    代表 Row 中的一个单元格,通过 Row 创建。

    java 复制代码
    Cell 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 自动关闭 WorkbookFileOutputStream,避免内存泄漏。
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 循环遍历 sheetrow 非常方便。
  • 单元格类型判断
    • 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 块中关闭。

  • 频繁调用 createCellsetCellValue 是正常的,但如果写入大量数据,可以考虑使用 SXSSF 和分批写入。

  • 避免在循环中创建不必要的样式对象,样式应复用。

  • OutOfMemoryError:写入超大数据时出现,应考虑使用 SXSSF。

  • InvalidFormatException :文件格式不匹配,例如用 XSSFWorkbook 读取 .xls 文件。

  • NullPointerException:未正确初始化单元格或行。


第三部分:Word 操作基础(XWPF)

既然你已经熟练掌握了 POI 操作 Excel(.xlsx)的方法,那么学习操作 Word(.docx)会非常轻松。POI 操作 .docx 的核心思想与操作 Excel 高度一致,只是将 XSSFWorkbook(工作簿)换成了 XWPFDocument(文档)

下面我们基于你已有的知识,快速掌握 XWPF 的使用。

3.1 核心类:从 XSSFXWPF 的映射

你可以通过这个对比表格,瞬间理解 .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; // 替换后跳出循环
            }
        }
    }
}

代码解释

  1. 加载模板 :像读取普通文件一样加载 template.docx
  2. 准备数据 :用一个 Map 存储占位符和真实值的对应关系。
  3. 遍历替换 :核心在于 replaceInParagraph 方法。它检查每个段落的文本是否包含占位符,如果包含,则获取该段落的第一个 Run(文本片段)并用替换后的完整文本覆盖 。
    • 注意 :上述替换方式会丢失原段落中除第一个 Run 外的格式。对于复杂的模板,可能需要更精细地遍历和操作每一个 Run,但理解这个基础版本是第一步。

3.4 处理更复杂的元素:图片

就像 Excel 中可以操作表格一样,Word 中也能操作表格 。

  • 插入图片

    java 复制代码
    XWPFParagraph 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 单位 。

相关推荐
java1234_小锋1 小时前
Java高频面试题:怎么实现Redis的高可用?
java·开发语言·redis
笨蛋不要掉眼泪1 小时前
Spring Cloud Gateway 核心实战:断言(Predicate)的长短写法与自定义工厂详解
java·前端·微服务·架构
A懿轩A1 小时前
【Maven 构建工具】Maven + JUnit5 单元测试实战:测试级别、注解、断言与 Maven test 阶段
java·单元测试·maven
Coder_Boy_2 小时前
以厨房连锁故事为引,梳理Java后端全技术脉络(JVM到云原生,总结篇)
java·jvm·spring boot·分布式·spring·云原生
Zhu_S W2 小时前
Docker 完全指南:Java 开发者的容器化实践
java·docker·容器
Zhu_S W2 小时前
EasyExcel动态表头详解
java·linux·windows
敲代码的哈吉蜂2 小时前
Nginx配置文件的管理及优化参数
java·服务器·nginx
XiaoLeisj2 小时前
Android RecyclerView 实战:从基础列表到多类型 Item、分割线与状态复用问题
android·java
勇往直前plus4 小时前
从文件到屏幕:Python/java 字符编码、解码、文本处理的底层逻辑解析
java·开发语言·python