杰瑞科技汇

Java操作Excel,POI如何实现?

目录

  1. 什么是 Apache POI?
  2. 准备工作:添加 Maven 依赖
  3. 核心概念:Workbook, Sheet, Row, Cell
  4. 完整代码示例:创建、读取、更新、删除
  5. 常用操作详解
    • 创建新 Excel 文件
    • 读取现有 Excel 文件
    • 写入数据(单元格样式、日期、公式)
    • 处理不同版本的 Excel (.xls vs .xlsx)
  6. 性能优化与最佳实践
    • SXSSF (流式 API):处理大数据量
    • 事件模型 (SAX):解析超大文件
  7. 常见问题与解决方案
    • OutOfMemoryError 错误
    • 读取 .xlsx 文件报错 Invalid header signature
    • 单元格样式问题

什么是 Apache POI?

Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,由 Apache 软件基金会维护,它允许 Java 程序读取、写入和操作 Microsoft Office 格式的文件,其中最核心的就是 Excel。

Java操作Excel,POI如何实现?-图1
(图片来源网络,侵删)

主要支持两种格式的 Excel:

  • .xls: Excel 97-2003 格式,使用 HSSF (Horrible Spreadsheet Format) API。
  • .xlsx: Excel 2007 及以后版本,基于 Office Open XML 格式,使用 XSSF API。

准备工作:添加 Maven 依赖

在你的 pom.xml 文件中添加 POI 的依赖,为了同时支持 .xls.xlsx,我们通常会添加 poipoi-ooxml

<dependencies>
    <!-- 支持 .xls 格式 -->
    <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>
    <!-- 用于处理 XML,是 poi-ooxml 的依赖 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-lite</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

核心概念

理解 POI 操作 Excel 的核心思想至关重要,就像操作一个数据库表。

  • Workbook (工作簿): 代表一个 Excel 文件,它是最高层级的对象。
    • 对于 .xls 文件,实现类是 HSSFWorkbook
    • 对于 .xlsx 文件,实现类是 XSSFWorkbook
  • Sheet (工作表): 代表 Workbook 中的一个工作表,"Sheet1", "Sheet2"。
  • Row (行): 代表 Sheet 中的某一行,行号从 0 开始。
  • Cell (单元格): 代表 Row 中的某一个单元格,列号也从 0 开始。

操作流程:获取 Workbook -> 选择 Sheet -> 选择 Row -> 操作 Cell

Java操作Excel,POI如何实现?-图2
(图片来源网络,侵删)

完整代码示例

这个示例将演示如何创建一个 Excel 文件,写入数据,然后读取它,并进行更新。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
public class PoiDemo {
    public static void main(String[] args) {
        String filePath = "D:/temp/poi_demo.xlsx";
        // 1. 创建 Excel 并写入数据
        createExcel(filePath);
        // 2. 读取 Excel 数据
        readExcel(filePath);
        // 3. 更新 Excel 数据
        updateExcel(filePath);
    }
    /**
     * 创建一个新的 Excel 文件并写入数据
     */
    public static void createExcel(String filePath) {
        // XSSFWorkbook 用于处理 .xlsx 文件
        Workbook workbook = new XSSFWorkbook();
        // HSSFWorkbook 用于处理 .xls 文件
        // Workbook workbook = new HSSFWorkbook();
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            // 创建一个工作表
            Sheet sheet = workbook.createSheet("员工信息");
            // 创建表头
            Row headerRow = sheet.createRow(0);
            headerRow.createCell(0).setCellValue("ID");
            headerRow.createCell(1).setCellValue("姓名");
            headerRow.createCell(2).setCellValue("年龄");
            headerRow.createCell(3).setCellValue("入职日期");
            // 创建数据行
            Row dataRow1 = sheet.createRow(1);
            dataRow1.createCell(0).setCellValue(1);
            dataRow1.createCell(1).setCellValue("张三");
            dataRow1.createCell(2).setCellValue(28);
            // 处理日期类型
            Cell dateCell = dataRow1.createCell(3);
            dateCell.setCellValue(new Date());
            // 设置日期格式
            CellStyle dateStyle = workbook.createCellStyle();
            CreationHelper createHelper = workbook.getCreationHelper();
            dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
            dateCell.setCellStyle(dateStyle);
            // 创建数据行
            Row dataRow2 = sheet.createRow(2);
            dataRow2.createCell(0).setCellValue(2);
            dataRow2.createCell(1).setCellValue("李四");
            dataRow2.createCell(2).setCellValue(30);
            dataRow2.createCell(3).setCellValue(new Date());
            // 自动调整列宽,以适应内容
            for (int i = 0; i < 4; i++) {
                sheet.autoSizeColumn(i);
            }
            // 写入文件
            workbook.write(fos);
            System.out.println("Excel 文件创建成功!路径: " + filePath);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 读取一个 Excel 文件
     */
    public static void readExcel(String filePath) {
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = WorkbookFactory.create(fis)) {
            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 遍历每一行
            for (Row row : sheet) {
                // 遍历每一个单元格
                for (Cell cell : row) {
                    // 根据单元格类型获取值
                    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();
        }
    }
    /**
     * 更新一个已有的 Excel 文件
     */
    public static void updateExcel(String filePath) {
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = WorkbookFactory.create(fis);
             FileOutputStream fos = new FileOutputStream(filePath)) {
            Sheet sheet = workbook.getSheetAt(0);
            // 获取第3行(索引为2)
            Row row = sheet.getRow(2);
            // 获取第2列(索引为1)的单元格,如果不存在则创建
            Cell cell = row.getCell(1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
            // 更新值
            cell.setCellValue("王五");
            // 写回文件
            workbook.write(fos);
            System.out.println("Excel 文件更新成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

常用操作详解

创建新 Excel 文件

// 1. 创建 Workbook 对象
Workbook workbook = new XSSFWorkbook(); // .xlsx
// Workbook workbook = new HSSFWorkbook(); // .xls
// 2. 创建 Sheet
Sheet sheet = workbook.createSheet("新工作表");
// 3. 创建 Row
Row row = sheet.createRow(0); // 第一行
// 4. 创建 Cell 并赋值
Cell cell = row.createCell(0);
cell.setCellValue("Hello, POI!");
// 5. 写入文件
try (FileOutputStream fos = new FileOutputStream("output.xlsx")) {
    workbook.write(fos);
}

读取现有 Excel 文件

使用 WorkbookFactory.create() 是最通用的方法,它可以自动识别 .xls 还是 .xlsx

try (FileInputStream fis = new FileInputStream("data.xlsx");
     Workbook workbook = WorkbookFactory.create(fis)) {
    Sheet sheet = workbook.getSheet("Sheet1");
    // 或者 sheet = workbook.getSheetAt(0); // 按索引获取
    // 获取总行数
    int rowCount = sheet.getPhysicalNumberOfRows();
    // 获取总列数 (以第一行为准)
    int colCount = sheet.getRow(0).getPhysicalNumberOfCells();
    for (int i = 0; i < rowCount; i++) {
        Row row = sheet.getRow(i);
        for (int j = 0; j < colCount; j++) {
            Cell cell = row.getCell(j);
            // ... (使用 switch-case 处理不同类型)
        }
    }
}

写入数据(单元格样式、日期、公式)

Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("样式示例");
// 创建样式
CellStyle style = workbook.createCellStyle();
// 背景颜色
style.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 字体
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 14);
style.setFont(font);
// 边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 水平居中
style.setAlignment(HorizontalAlignment.CENTER);
// 应用样式
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("这是带样式的单元格");
cell.setCellStyle(style);
// 写入公式
Cell formulaCell = row.createCell(1);
formulaCell.setCellFormula("SUM(A1:A10)"); // 假设 A1 到 A10 有数字
// 写入日期
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy年MM月dd日"));
Cell dateCell = row.createCell(2);
dateCell.setCellValue(new Date());
dateCell.setCellStyle(dateStyle);

处理不同版本的 Excel

文件扩展名 POI API 内存模型 备注
.xls HSSFWorkbook 所有数据加载到内存 最多支持 65536 行,256 列,旧版本。
.xlsx XSSFWorkbook 所有数据加载到内存 理论上支持无限行和列,但大文件会导致 OOM
.xlsx (大数据) SXSSFWorkbook 磁盘缓存(滑动窗口) 专门为处理大数据量设计,避免 OOM,推荐用于导出。
.xls (大数据) HSSFWorkbook 无对应 POI 没有为 .xls 提供流式 API,大数据量建议转用 .xlsx

性能优化与最佳实践

SXSSF (流式 API) - 处理大数据量导出

当需要导出几十万甚至上百万行数据时,使用 XSSFWorkbook 会导致内存溢出。SXSSFWorkbook (Streaming Usermodel API) 通过将数据写入临时文件,只在内存中保留一部分数据(一个滑动窗口),从而极大地减少了内存消耗。

核心特点:

Java操作Excel,POI如何实现?-图3
(图片来源网络,侵删)
  • 低内存: 内存占用与行数无关,只与窗口大小有关。
  • 只写: SXSSFWorkbook 主要用于写操作,读操作仍然使用 XSSFHSSF
  • 临时文件: 默认情况下,临时文件会保存在系统临时目录 (java.io.tmpdir),记得在完成后清理。

示例:导出 10 万行数据

import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFCell;
public class BigDataExport {
    public static void main(String[] args) throws Exception {
        // 1. 创建 SXSSFWorkbook,参数是窗口大小(在内存中保留的行数)
        // -1 表示使用默认值 (100)
        Workbook workbook = new SXSSFWorkbook(100); // window size
        Sheet sheet = workbook.createSheet("大数据");
        // 2. 像使用 XSSFWorkbook 一样写入数据
        for (int rownum = 0; rownum < 100000; rownum++) {
            Row row = sheet.createRow(rownum);
            for (int cellnum = 0; cellnum < 10; cellnum++) {
                Cell cell = row.createCell(cellnum);
                cell.setCellValue("Row " + rownum + ", Cell " + cellnum);
            }
            // 可以手动将行刷出到磁盘
            if (rownum % 100 == 0) {
                ((SXSSFSheet) sheet).flushRows(); // 100 rows in memory at any time
            }
        }
        // 3. 写入文件
        try (FileOutputStream out = new FileOutputStream("D:/temp/big_data.xlsx")) {
            workbook.write(out);
        }
        // 4. 重要!清理临时文件
        ((SXSSFWorkbook) workbook).dispose();
        System.out.println("大数据导出完成!");
    }
}

事件模型 (SAX) - 解析超大文件

如果只是需要读取一个超大 Excel 文件(1GB 以上),并且只需要其中一部分数据,使用 XSSF 仍然会占用大量内存,这时可以使用 SAX (Simple API for XML) 事件模型

它不会将整个文件加载到内存,而是像解析 XML 一样,逐行触发事件(如 startRow, endRow, cellValue 等),你只需要监听这些事件并处理数据即可。

这种方式性能最高,但编程模型最复杂,需要自己处理事件,通常用于 ETL 或数据迁移场景。


常见问题与解决方案

OutOfMemoryError 错误

  • 原因: 使用 XSSFWorkbookHSSFWorkbook 处理大文件时,所有数据都被加载到 JVM 堆内存中。
  • 解决方案:
    1. 首选: 使用 SXSSFWorkbook 进行大数据量导出
    2. 次选: 使用 SAX 事件模型进行大数据量读取
    3. 增加JVM堆内存 (-Xmx1024m 等),这只是治标不治本,不推荐。

读取 .xlsx 文件报错 Invalid header signature

  • 原因: 通常是因为 .xlsx 文件正在被 Excel 软件打开,导致文件被锁定或损坏。
  • 解决方案: 关闭 Excel 软件,确保文件没有被其他程序占用。

单元格样式问题(如数字变成科学计数法)

  • 原因: POI 默认不会为数字单元格设置格式,当数字过长时,它会自动转换为科学计数法或文本格式。
  • 解决方案: 在写入数字之前,手动为单元格设置一个合适的样式,CellStylesetDataFormat()
CellStyle style = workbook.createCellStyle();
style.setDataFormat(workbook.createDataFormat().getFormat("@")); // @ 表示文本格式
cell.setCellStyle(style);
cell.setCellValue(1234567890123L); // 长数字

场景 推荐的 POI API 特点
读取/写入小/中文件 XSSFWorkbook / HSSFWorkbook 简单易用,功能完整
导出大数据文件 (.xlsx) SXSSFWorkbook 内存友好,流式写入,避免 OOM
读取超大文件 (.xlsx) SAX (事件模型) 性能最高,内存占用极低,编程复杂

对于大多数日常应用,掌握 XSSFWorkbookSXSSFWorkbook 就足以应对所有挑战,希望这份详细的指南能帮助你熟练地在 Java 中使用 POI 操作 Excel!

分享:
扫描分享到社交APP
上一篇
下一篇