杰瑞科技汇

excel poi java 写

目录

  1. 环境准备
    • 添加 Maven 依赖
    • 了解 POI 模块
  2. 核心概念
    • Workbook (工作簿)
    • Sheet (工作表)
    • Row (行)
    • Cell (单元格)
  3. 基础操作
    • 创建 Excel 文件 (XLSX / XLS)
    • 读取 Excel 文件
    • 修改现有 Excel 文件
    • 设置单元格样式 (字体、颜色、边框等)
  4. 进阶操作
    • 处理日期
    • 处理公式
    • 合并单元格
    • 设置列宽和行高
    • 单元格数据类型
  5. 性能优化 (SXSSF)

    处理大数据量

    excel poi java 写-图1
    (图片来源网络,侵删)
  6. 总结与最佳实践

环境准备

添加 Maven 依赖

在你的 pom.xml 文件中添加以下依赖。推荐使用最新的稳定版本

<dependencies>
    <!-- Apache POI for XLSX (Office 2007+) -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- Required for parsing OOXML (XLSX) files -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- Required for OPC package handling (XLSX) -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-lite</artifactId>
        <version>5.2.5</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- Optional: For parsing XLS (Legacy Office 97-2003) format -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

了解 POI 模块

  • poi: 核心,用于处理 .xls 格式 (Excel 97-2003)。
  • poi-ooxml: 用于处理 .xlsx (Excel 2007+) 和 .xlsm (带宏的 Excel) 格式。
  • poi-ooxml-lite: poi-ooxml 的轻量级版本,如果不需要 XWPF (Word) 和 XSLF (PPT) 可以只加这个。
  • poi-scratchpad: 提供一些实验性或遗留格式的支持。

推荐做法: 即使你只处理 .xlsx,也建议同时引入 poipoi-ooxml,因为它们之间有依赖关系。


核心概念

理解 POI 操作 Excel 的“树状结构”至关重要:

  • Workbook (工作簿): 代表整个 Excel 文件,一个 Excel 文件就是一个 Workbook 对象。
    • XSSFWorkbook: 用于 .xlsx 文件。
    • HSSFWorkbook: 用于 .xls 文件。
  • Sheet (工作表): 代表 Workbook 中的一个表(Sheet1, Sheet2...)。
  • Row (行): 代表 Sheet 中的一行,行号从 0 开始。
  • Cell (单元格): 代表 Row 中的一个格子,列号从 0 开始。

操作流程通常是:创建/获取 Workbook -> 创建/获取 Sheet -> 创建/获取 Row -> 创建/获取 Cell -> 设置/获取值。

excel poi java 写-图2
(图片来源网络,侵删)

基础操作

创建 Excel 文件 (XLSX)

下面是一个完整的示例,创建一个 .xlsx 文件,写入一些数据,并设置基本样式。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class CreateExcelExample {
    public static void main(String[] args) {
        // 1. 创建一个 XSSFWorkbook 对象 (代表 .xlsx 文件)
        Workbook workbook = new XSSFWorkbook();
        // 2. 创建一个工作表,名称为 "员工信息"
        Sheet sheet = workbook.createSheet("员工信息");
        // 3. 创建标题行 (第0行)
        Row headerRow = sheet.createRow(0);
        // 4. 创建标题单元格并设置值
        CellStyle headerStyle = createHeaderStyle(workbook); // 创建标题样式
        createCell(headerRow, 0, "员工ID", headerStyle);
        createCell(headerRow, 1, "姓名", headerStyle);
        createCell(headerRow, 2, "部门", headerStyle);
        createCell(headerRow, 3, "入职日期", headerStyle);
        // 5. 创建数据行
        Row dataRow1 = sheet.createRow(1);
        dataRow1.createCell(0).setCellValue(1001);
        dataRow1.createCell(1).setCellValue("张三");
        dataRow1.createCell(2).setCellValue("研发部");
        dataRow1.createCell(3).setCellValue(new java.util.Date()); // 日期
        Row dataRow2 = sheet.createRow(2);
        dataRow2.createCell(0).setCellValue(1002);
        dataRow2.createCell(1).setCellValue("李四");
        dataRow2.createCell(2).setCellValue("市场部");
        dataRow2.createCell(3).setCellValue(new java.util.Date());
        // 6. 自动调整列宽
        for (int i = 0; i < 4; i++) {
            sheet.autoSizeColumn(i);
        }
        // 7. 将 workbook 写入文件
        try (FileOutputStream fileOut = new FileOutputStream("employees.xlsx")) {
            workbook.write(fileOut);
            System.out.println("Excel 文件 'employees.xlsx' 创建成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 8. 关闭 workbook,释放资源
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 创建一个通用的创建单元格方法,可以应用样式
     */
    private static void createCell(Row row, int column, String value, CellStyle style) {
        Cell cell = row.createCell(column);
        cell.setCellValue(value);
        cell.setCellStyle(style);
    }
    /**
     * 创建标题行样式
     */
    private static CellStyle createHeaderStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        // 设置背景色
        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 设置字体
        Font font = workbook.createFont();
        font.setBold(true);
        style.setFont(font);
        // 设置边框
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        return style;
    }
}

读取 Excel 文件

读取操作与写入相反,是从文件流中解析出 Workbook 对象,然后逐层遍历。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelExample {
    public static void main(String[] args) {
        String filePath = "employees.xlsx";
        try (FileInputStream fileIn = new FileInputStream(filePath);
             Workbook workbook = new XSSFWorkbook(fileIn)) {
            // 1. 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0); // 或 getSheet("员工信息")
            // 2. 遍历所有行
            for (Row row : sheet) {
                // 跳过标题行 (如果需要)
                if (row.getRowNum() == 0) {
                    continue;
                }
                // 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("UNKNOWN\t");
                    }
                }
                System.out.println(); // 换行
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

修改现有 Excel 文件

修改的关键在于使用 FileInputStream 读取现有文件,然后使用 FileOutputStream 写回同一个文件注意: 这会直接覆盖原文件,操作前最好备份。

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 ModifyExcelExample {
    public static void main(String[] args) {
        String filePath = "employees.xlsx";
        try (FileInputStream fileIn = new FileInputStream(filePath);
             Workbook workbook = new XSSFWorkbook(fileIn);
             FileOutputStream fileOut = new FileOutputStream(filePath)) {
            Sheet sheet = workbook.getSheet("员工信息");
            // 假设我们要修改第二行第二列(姓名)的数据
            Row rowToModify = sheet.getRow(1); // 行号从0开始,第二行是1
            if (rowToModify != null) {
                Cell cellToModify = rowToModify.getCell(1); // 列号从0开始,第二列是1
                if (cellToModify == null) {
                    cellToModify = rowToModify.createCell(1);
                }
                cellToModify.setCellValue("王五");
                System.out.println("已将第二行姓名修改为 '王五'");
            }
            // 写回文件
            workbook.write(fileOut);
            System.out.println("Excel 文件修改成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

设置单元格样式

样式是通过 CellStyleFont 对象来设置的,创建一次样式后,可以重复应用到多个单元格上,以提高效率。

// ... (在创建 Workbook 之后)
// 1. 创建一个样式对象
CellStyle style = workbook.createCellStyle();
// 2. 设置背景色
style.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 3. 设置字体
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 12);
font.setBold(true);
font.setColor(IndexedColors.WHITE.getIndex());
style.setFont(font);
// 4. 设置边框
style.setBorderBottom(BorderStyle.MEDIUM);
style.setBorderTop(BorderStyle.MEDIUM);
style.setBorderLeft(BorderStyle.MEDIUM);
style.setBorderRight(BorderStyle.MEDIUM);
// 5. 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER); // 水平居中
style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
// 6. 将样式应用到单元格
Cell cell = row.createCell(0);
cell.setCellValue("带样式的文本");
cell.setCellStyle(style);

进阶操作

处理日期

POI 不会自动将 Excel 中的数字识别为日期,你需要手动判断和转换。

// 写入日期
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
Cell dateCell = row.createCell(3);
dateCell.setCellValue(new java.util.Date());
dateCell.setCellStyle(dateStyle);
// 读取日期
if (DateUtil.isCellDateFormatted(cell)) {
    java.util.Date date = cell.getDateCellValue();
    System.out.println("这是一个日期: " + date);
} else {
    // 可能是一个序列号,可以手动转换
    double numericValue = cell.getNumericCellValue();
    java.util.Date date = DateUtil.getJavaDate(numericValue);
    System.out.println("这是一个日期序列号: " + date);
}

处理公式

// 设置公式
Cell formulaCell = row.createCell(4);
formulaCell.setCellFormula("SUM(A1:A2)"); // 假设 A1 和 A2 是数字
// 读取公式
if (cell.getCellType() == CellType.FORMULA) {
    String formula = cell.getCellFormula();
    System.out.println("单元格中的公式是: " + formula);
    // 计算公式的结果
    if (workbook instanceof FormulaEvaluator) {
        FormulaEvaluator evaluator = ((Workbook) workbook).getCreationHelper().createFormulaEvaluator();
        CellValue result = evaluator.evaluate(cell);
        System.out.println("公式计算结果是: " + result.formatAsString());
    }
}

合并单元格

// 合并从 (0,0) 到 (4,0) 的区域,即第1列的前5行
sheet.addMergedRegion(new CellRangeAddress(0, 4, 0, 0));
// 在合并区域的左上角单元格写入值
Cell mergedCell = sheet.getRow(0).getCell(0);
if (mergedCell == null) {
    mergedCell = sheet.getRow(0).createCell(0);
}
mergedCell.setCellValue("合并后的单元格");

设置列宽和行高

// 设置第2列的宽度,宽度是字符宽度的256倍
sheet.setColumnWidth(1, 20 * 256); // 设置为20个字符宽
// 设置第3行的高度,高度是1/20个点
sheet.getRow(2).setHeight((short) (30 * 20)); // 设置为30个点高

性能优化 (SXSSF)

当处理大数据量(例如超过 10 万行)时,标准的 XSSFWorkbook 会将所有数据都加载到内存中,导致 OutOfMemoryError (内存溢出)。

解决方案是使用 SXSSFWorkbook (Streaming Usermodel API),它的工作原理是:

  1. 在内存中只保留一定数量的行(默认 100 行)。
  2. 当行数超过这个限制时,将最旧的行写入一个临时文件(.tmp)。
  3. 将所有数据合并成一个 .xlsx 文件。

示例:导出 50 万行数据

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 BigDataExcelExample {
    public static void main(String[] args) throws IOException {
        // 1. 创建 SXSSFWorkbook,参数 100 表示在内存中保留100行
        Workbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("大数据报表");
        // 2. 创建样式 (注意:SXSSF 对样式的支持有限,样式对象不能在行之间共享)
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true);
        style.setFont(font);
        // 3. 写入数据
        for (int i = 0; i < 500000; i++) {
            Row row = sheet.createRow(i);
            row.createCell(0).setCellValue("ID " + i);
            row.createCell(1).setCellValue("用户 " + i);
            if (i == 0) { // 只给第一行设置样式
                row.getCell(0).setCellStyle(style);
                row.getCell(1).setCellStyle(style);
            }
            // 模拟进度
            if (i % 10000 == 0) {
                System.out.println("已处理 " + i + " 行...");
            }
        }
        // 4. 写入文件
        try (FileOutputStream out = new FileOutputStream("big_data_report.xlsx")) {
            workbook.write(out);
            System.out.println("大数据Excel文件生成成功!");
        } finally {
            // 5. 清理临时文件 (非常重要!)
            ((SXSSFWorkbook) workbook).dispose();
            if (workbook != null) {
                workbook.close();
            }
        }
    }
}

注意:

  • SXSSFWorkbook 是只写的,不支持读取和修改。
  • 不能使用 Sheet.autoSizeColumn(),因为它需要遍历所有行,这在流式API中是不可能的,你需要预估列宽或手动设置。
  • 样式对象不能在不同行之间共享,否则会抛出异常。

总结与最佳实践

  1. 选择正确的 API:

    • .xlsx 文件: XSSFWorkbook
    • .xls 文件: HSSFWorkbook
    • 大数据量 (只写): SXSSFWorkbook
  2. 资源管理: 使用 try-with-resources 语句来管理 WorkbookFileOutputStream/FileInputStream,确保文件流和对象被正确关闭。

  3. 样式复用: 对于 XSSFWorkbook,创建一个 CellStyleFont 对象后,可以多次调用 cell.setCellStyle(style) 来应用到不同单元格,避免重复创建,提高性能。

  4. 数据类型: 写入数据时,使用 setCellValue() 的正确重载方法(如 setCellValue(String), setCellValue(double)),读取数据时,务必使用 getCellType() 判断类型,再调用对应的 getXXXCellValue() 方法。

  5. 异常处理: 文件操作和 IO 操作都可能抛出 IOException,务必进行捕获和处理。

  6. 大数据处理: 当你预见到数据量会很大时,直接使用 SXSSFWorkbook,不要等到内存溢出再重构。

希望这份详细的指南能帮助你掌握使用 Java 和 Apache POI 操作 Excel!

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