杰瑞科技汇

java 操作excel poi

Apache POI 是一个强大的 Java API,它允许你创建、修改和显示 MS Office 格式的文件,包括 .xls (Excel 97-2003) 和 .xlsx (Excel 2007+) 格式。

java 操作excel poi-图1
(图片来源网络,侵删)

目录

  1. 准备工作:添加 POI 依赖
  2. 核心概念:XSSFWorkbook vs HSSFWorkbook
  3. 基本操作(以 .xlsx 为例)
    • 1 创建新的 Excel 文件并写入数据
    • 2 读取已有的 Excel 文件
    • 3 修改已有的 Excel 文件
  4. 常用功能详解
    • 1 样式设置(字体、颜色、边框)
    • 2 合并单元格
    • 3 调整列宽
    • 4 处理日期
    • 5 下拉列表
  5. 性能优化:SXSSFWorkbook
  6. 完整示例代码
  7. 总结与注意事项

准备工作:添加 POI 依赖

你需要在你的项目中添加 Apache POI 的依赖,如果你使用 Maven,请在 pom.xml 文件中添加以下依赖:

<!-- 针对 .xlsx (OOXML) 格式的依赖 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.5</version> <!-- 建议使用最新稳定版 -->
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.5</version>
</dependency>
<!-- 针对 .xls (BIFF) 格式的依赖 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.5</version>
</dependency>
<!-- 如果需要处理公式,可能需要这个 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>5.2.5</version>
</dependency>

如果你使用 Gradle,在 build.gradle 文件中添加:

implementation 'org.apache.poi:poi:5.2.5'
implementation 'org.apache.poi:poi-ooxml:5.2.5'

核心概念:XSSFWorkbook vs HSSFWorkbook

这是使用 POI 时最重要的区分点,直接决定了你能操作的文件格式。

特性 HSSFWorkbook XSSFWorkbook
文件格式 .xls (Excel 97-2003) .xlsx (Excel 2007 及以上)
底层结构 基于 BIFF 格式,所有数据都加载到内存中 基于 OOXML (Open Office XML) 格式,结构更复杂
内存占用 较低,但处理大文件时会非常消耗内存 较高,但提供了流式 API (SXSSF) 来优化大文件
POI 模块 poi poi-ooxml

选择建议:

java 操作excel poi-图2
(图片来源网络,侵删)
  • 新项目强烈推荐使用 .xlsx 格式,因为它功能更强大、文件更小、结构更标准,使用 XSSFWorkbook
  • 旧系统兼容:如果必须与旧版 Excel (2003及以前) 兼容,则使用 .xls 格式和 HSSFWorkbook

基本操作(以 .xlsx 为例)

1 创建新的 Excel 文件并写入数据

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class CreateExcel {
    public static void main(String[] args) {
        // 1. 创建一个新的 XSSFWorkbook 对象,代表一个 .xlsx 文件
        Workbook workbook = new XSSFWorkbook();
        try {
            // 2. 创建一个工作表,名为 "Sheet1"
            Sheet sheet = workbook.createSheet("员工信息");
            // 3. 创建标题行 (Row 0)
            Row headerRow = sheet.createRow(0);
            // 创建单元格并设置值
            headerRow.createCell(0).setCellValue("ID");
            headerRow.createCell(1).setCellValue("姓名");
            headerRow.createCell(2).setCellValue("年龄");
            headerRow.createCell(3).setCellValue("入职日期");
            // 4. 创建数据行
            Row dataRow1 = sheet.createRow(1);
            dataRow1.createCell(0).setCellValue(1);
            dataRow1.createCell(1).setCellValue("张三");
            dataRow1.createCell(2).setCellValue(28);
            dataRow1.createCell(3).setCellValue(setDateCellValue(workbook, "2025-05-15")); // 设置日期
            Row dataRow2 = sheet.createRow(2);
            dataRow2.createCell(0).setCellValue(2);
            dataRow2.createCell(1).setCellValue("李四");
            dataRow2.createCell(2).setCellValue(32);
            dataRow2.createCell(3).setCellValue(setDateCellValue(workbook, "2025-08-20"));
            // 5. 调整列宽以适应内容
            sheet.autoSizeColumn(0);
            sheet.autoSizeColumn(1);
            sheet.autoSizeColumn(2);
            sheet.autoSizeColumn(3);
            // 6. 将 workbook 写入文件输出流
            try (FileOutputStream fileOut = new FileOutputStream("D:/temp/员工信息.xlsx")) {
                workbook.write(fileOut);
                System.out.println("Excel 文件创建成功!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭 workbook,释放资源
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    // 辅助方法:创建日期格式的单元格
    private static Cell setDateCellValue(Workbook workbook, String dateString) {
        CreationHelper createHelper = workbook.getCreationHelper();
        Cell cell = null; // 假设这个方法在某个 Row 的 createCell 后调用
        cell.setCellValue(createHelper.createFormula("DATEVALUE(\"" + dateString + "\")"));
        // 或者直接使用 Date 对象
        // cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").parse(dateString));
        // 应用日期格式
        CellStyle dateStyle = workbook.createCellStyle();
        dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
        cell.setCellStyle(dateStyle);
        return cell;
    }
}

2 读取已有的 Excel 文件

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcel {
    public static void main(String[] args) {
        String filePath = "D:/temp/员工信息.xlsx";
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = new XSSFWorkbook(fis)) {
            // 1. 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 2. 遍历所有行
            for (Row row : sheet) {
                // 3. 遍历当前行的所有单元格
                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("\t");
                    }
                }
                System.out.println(); // 换行
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3 修改已有的 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;
public class ModifyExcel {
    public static void main(String[] args) {
        String filePath = "D:/temp/员工信息.xlsx";
        String tempPath = "D:/temp/员工信息_temp.xlsx";
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = new XSSFWorkbook(fis);
             FileOutputStream fos = new FileOutputStream(tempPath)) {
            // 1. 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 2. 找到要修改的行(ID为2的行)
            for (Row row : sheet) {
                if (row.getCell(0).getNumericCellValue() == 2.0) {
                    // 3. 修改单元格内容
                    Cell nameCell = row.getCell(1);
                    nameCell.setCellValue("李四 (已更新)");
                    // 4. 也可以修改样式
                    CellStyle style = workbook.createCellStyle();
                    Font font = workbook.createFont();
                    font.setColor(IndexedColors.RED.getIndex());
                    style.setFont(font);
                    nameCell.setCellStyle(style);
                    break; // 找到后退出循环
                }
            }
            // 5. 将修改后的内容写入新文件
            workbook.write(fos);
            System.out.println("Excel 文件修改成功,已保存为: " + tempPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

常用功能详解

1 样式设置

样式通过 CellStyleFont 对象来创建和设置。

// 在 Workbook 中创建样式
CellStyle style = workbook.createCellStyle();
// 设置背景色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 设置字体
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 12);
font.setBold(true);
style.setFont(font);
// 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 将样式应用到单元格
cell.setCellStyle(style);

2 合并单元格

使用 SheetaddMergedRegion 方法。

// 合并从 (row1, col1) 到 (row2, col2) 的区域
// 参数是 CellRangeAddress 对象
CellRangeAddress range = new CellRangeAddress(0, 0, 0, 3); // 合并第1行的第1列到第4列
sheet.addMergedRegion(range);
// 在合并后的区域的第一个单元格写入数据
sheet.getRow(0).getCell(0).setCellValue("合并单元格标题");

3 调整列宽

// 自动调整列宽
sheet.autoSizeColumn(0); // 调整第一列
sheet.autoSizeColumn(1); // 调整第二列
// 手动设置列宽(单位是 1/256 个字符的宽度)
sheet.setColumnWidth(0, 20 * 256); // 设置第一列宽度为20个字符

4 处理日期

直接设置 Date 对象到单元格是不够的,必须配合 CellStyle 来指定显示格式。

java 操作excel poi-图3
(图片来源网络,侵删)
// 1. 创建单元格
Cell cell = row.createCell(3);
// 2. 创建日期样式
CellStyle dateStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy年mm月dd日 hh:mm:ss"));
// 3. 创建日期对象并设置到单元格
Date date = new Date();
cell.setCellValue(date);
// 4. 应用样式
cell.setCellStyle(dateStyle);

5 下拉列表

通过数据有效性(Data Validation)实现。

// 1. 创建一个隐藏的 sheet 来存放下拉列表的选项值
Sheet hiddenSheet = workbook.createSheet("hiddenOptions");
Row hiddenRow = hiddenSheet.createRow(0);
hiddenRow.createCell(0).setCellValue("选项A");
hiddenRow.createCell(1).setCellValue("选项B");
hiddenRow.createCell(2).setCellValue("选项C");
// 2. 定义名称,指向选项区域
Name namedCell = workbook.createName();
namedCell.setNameName("dropdownList");
namedCell.setRefersToFormula("hiddenOptions!$A$1:$C$1");
// 3. 在目标 sheet 的目标单元格上设置数据有效性
Sheet mainSheet = workbook.getSheet("主数据");
CellAddress address = new CellAddress(1, 1); // 第2行,第2列
DataValidationHelper validationHelper = mainSheet.getDataValidationHelper();
DataValidationConstraint constraint = validationHelper.createFormulaListConstraint("dropdownList");
DataValidation validation = validationHelper.createValidation(constraint, new CellRangeAddressList(1, 1, 1, 1)); // 只对 B2 单元格有效
// 防止输入下拉列表中没有的值
validation.setShowErrorBox(true);
mainSheet.addValidationData(validation);

性能优化:SXSSFWorkbook

当处理非常大的 Excel 文件(几十万行数据)时,XSSFWorkbook 会将所有数据都加载到内存中,很容易导致 OutOfMemoryError

SXSSFWorkbook (Streaming Usermodel API) 是为此而生的解决方案,它基于 XSSFWorkbook,但采用临时文件滑动窗口的机制,只在内存中保留一部分行,其余的行会写入临时磁盘文件,从而极大地减少内存消耗。

核心思想SXSSFWorkbook 会创建一个 .xlsx 文件,但它在内存中只保留 windowSize 数量的行,当写入的行数超过这个窗口时,最旧的行会被“刷新”到磁盘上。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import java.io.FileOutputStream;
import java.io.IOException;
public class BigExcelExample {
    public static void main(String[] args) throws IOException {
        // 1. 创建 SXSSFWorkbook,设置窗口大小为 100 (内存中保留100行)
        Workbook workbook = new SXSSFWorkbook(100);
        try {
            Sheet sheet = workbook.createSheet("大数据量");
            // 2. 写入大量数据 (10 万行)
            for (int i = 0; i < 100000; i++) {
                Row row = sheet.createRow(i);
                row.createCell(0).setCellValue("ID " + i);
                row.createCell(1).setCellValue("数据 " + i);
                // 每1000行打印一次进度
                if (i % 1000 == 0) {
                    System.out.println("已写入 " + i + " 行...");
                }
            }
            // 3. 写入文件
            try (FileOutputStream out = new FileOutputStream("D:/temp/大数据量.xlsx")) {
                workbook.write(out);
                System.out.println("大数据量 Excel 文件创建成功!");
            }
        } finally {
            // 4. 清理临时文件!非常重要!
            ((SXSSFWorkbook) workbook).dispose();
        }
    }
}

注意

  • SXSSFWorkbook 不支持随机访问和修改,它只能按顺序写入。
  • 必须调用 dispose() 方法来删除在磁盘上生成的临时文件。

完整示例代码

这里提供一个结合了创建、样式、合并、日期和自动列宽的完整示例。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
public class CompleteExample {
    public static void main(String[] args) {
        Workbook workbook = new XSSFWorkbook();
        try {
            Sheet sheet = workbook.createSheet("综合示例");
            // 1. 创建带样式的标题
            Row titleRow = sheet.createRow(0);
            Cell titleCell = titleRow.createCell(0);
            titleCell.setCellValue("综合示例报表");
            // 创建标题样式
            CellStyle titleStyle = workbook.createCellStyle();
            Font titleFont = workbook.createFont();
            titleFont.setFontHeightInPoints((short) 16);
            titleFont.setBold(true);
            titleStyle.setFont(titleFont);
            titleStyle.setAlignment(HorizontalAlignment.CENTER);
            titleCell.setCellStyle(titleStyle);
            // 2. 合并标题单元格
            sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
            // 3. 创建表头
            Row headerRow = sheet.createRow(2);
            String[] headers = {"产品名称", "销售数量", "单价", "销售日期", "备注"};
            for (int i = 0; i < headers.length; i++) {
                Cell cell = headerRow.createCell(i);
                cell.setCellValue(headers[i]);
                // 应用表头样式 (可以单独创建)
                CellStyle headerStyle = workbook.createCellStyle();
                headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
                headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                cell.setCellStyle(headerStyle);
            }
            // 4. 创建数据行
            Object[][] data = {
                    {"笔记本电脑", 120, 5999.99, "2025-10-01", "热销款"},
                    {"无线鼠标", 350, 99.50, "2025-10-02", null},
                    {"机械键盘", 80, 399.00, "2025-10-03", "青轴"}
            };
            for (int i = 0; i < data.length; i++) {
                Row row = sheet.createRow(3 + i);
                for (int j = 0; j < data[i].length; j++) {
                    Object value = data[i][j];
                    Cell cell = row.createCell(j);
                    if (value instanceof String) {
                        cell.setCellValue((String) value);
                    } else if (value instanceof Double) {
                        cell.setCellValue((Double) value);
                    } else if (value instanceof Date) {
                        cell.setCellValue((Date) value);
                    }
                }
            }
            // 5. 设置日期格式
            CreationHelper createHelper = workbook.getCreationHelper();
            CellStyle dateStyle = workbook.createCellStyle();
            dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
            sheet.getRow(4).getCell(3).setCellStyle(dateStyle); // 应用到第一个日期单元格
            // 6. 自动调整列宽
            for (int i = 0; i < headers.length; i++) {
                sheet.autoSizeColumn(i);
            }
            // 7. 写入文件
            try (FileOutputStream fos = new FileOutputStream("D:/temp/综合示例.xlsx")) {
                workbook.write(fos);
                System.out.println("综合示例 Excel 文件创建成功!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

总结与注意事项

  1. 依赖选择:明确你的目标文件格式是 .xls 还是 .xlsx,选择对应的 HSSFWorkbookXSSFWorkbook
  2. 资源管理Workbook 和所有 InputStream/OutputStream 都应该在 try-finallytry-with-resources 块中关闭,以防止资源泄漏。
  3. 日期处理:设置日期值时,务必配合 CellStyle 来指定显示格式,否则可能显示为数字。
  4. 大文件处理:对于大数据量,请务必使用 SXSSFWorkbook,并记得在最后调用 dispose()
  5. 性能:频繁的样式创建会消耗性能,如果多个单元格需要相同的样式,应该创建一个 CellStyle 对象并重复使用,而不是为每个单元格都新建一个。
  6. 异常处理:文件操作(读写)可能会抛出 IOException,需要进行妥善处理。
分享:
扫描分享到社交APP
上一篇
下一篇