杰瑞科技汇

Java POI生成Excel时如何避免样式错乱?

什么是 Apache POI?

Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,由 Apache 软件基金会维护,它允许程序员使用 Java 代码来操作 Microsoft Office 格式的文件,包括:

Java POI生成Excel时如何避免样式错乱?-图1
(图片来源网络,侵删)
  • Excel (.xls, .xlsx)
  • Word (.doc, .docx)
  • PowerPoint (.ppt, .pptx)

对于我们生成 Excel 的需求,主要关注 POI 的 HSSFXSSF 模块。

POI 的核心模块

在开始之前,你需要理解 POI 针对 Excel 的不同版本有两个核心模块:

模块 扩展名 描述 优点 缺点
HSSF .xls 操作 Excel 97-2003 格式。 兼容性好,几乎所有 Excel 版本都能打开。 单个工作簿最多支持 65536 行、256 列,文件格式老旧。
XSSF .xlsx 操作 Excel 2007 及以上格式 (OOXML)。 支持超大文件(行数和列数限制极大),功能更丰富。 需要较高版本的 Java 支持,旧版 Excel 可能无法直接打开。
SXSSF .xlsx XSSF 的大数据量流式 API。 内存效率极高,适用于生成数百万行的超大文件。 不支持随机访问,已写入的数据不能修改或删除。

对于新项目,强烈推荐使用 XSSFSXSSF


环境准备 (Maven 依赖)

你需要在你的项目中添加 POI 的依赖,这里以 Maven 为例。

Java POI生成Excel时如何避免样式错乱?-图2
(图片来源网络,侵删)

1. 生成 .xlsx 文件 (推荐)

这是目前最常用的方式。

<dependencies>
    <!-- POI 核心依赖 -->
    <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>
    <!-- 为了支持一些高级特性,可能需要 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-lite</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

2. 生成 .xls 文件 (旧版)

如果你的用户还在使用非常古老的 Excel,你可能需要这个。

<dependencies>
    <!-- 仅需 poi 即可 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

核心概念和 API

在写代码之前,了解几个核心类非常重要:

  • Workbook: 工作簿,代表一个 Excel 文件,对于 .xlsx,它是 XSSFWorkbook;对于 .xls,它是 HSSFWorkbook
  • Sheet: 工作表,一个 Workbook 可以包含多个 Sheet (如 "Sheet1", "Sheet2")。
  • Row: 行,一个 Sheet 由多行组成。
  • Cell: 单元格,一个 Row 由多个单元格组成。
  • CellStyle: 单元格样式,可以设置字体、颜色、边框、对齐方式等。
  • Font: 字体,用于设置 CellStyle 中的字体样式。

完整代码示例 (生成 .xlsx 文件)

下面是一个完整的、可运行的 Java 示例,它会创建一个名为 students.xlsx 的文件,并包含一些格式化的数据。

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 PoiExcelExample {
    public static void main(String[] args) {
        // 1. 创建一个新的 Workbook (XSSFWorkbook for .xlsx)
        Workbook workbook = new XSSFWorkbook();
        // 2. 创建一个 Sheet,并命名
        Sheet sheet = workbook.createSheet("学生信息");
        // 3. 创建标题行 (Row 0)
        Row headerRow = sheet.createRow(0);
        // 4. 创建标题单元格并设置值
        headerRow.createCell(0).setCellValue("ID");
        headerRow.createCell(1).setCellValue("姓名");
        headerRow.createCell(2).setCellValue("年龄");
        headerRow.createCell(3).setCellValue("入学日期");
        headerRow.createCell(4).setCellValue("成绩");
        // 5. 创建一些样式 (可选)
        // 5.1 创建一个用于标题的样式
        CellStyle headerStyle = workbook.createCellStyle();
        Font headerFont = workbook.createFont();
        headerFont.setBold(true); // 加粗
        headerFont.setFontHeightInPoints((short) 12);
        headerStyle.setFont(headerFont);
        headerStyle.setAlignment(HorizontalAlignment.CENTER); // 居中对齐
        // 5.2 将样式应用到标题行
        for (Cell cell : headerRow) {
            cell.setCellStyle(headerStyle);
        }
        // 6. 创建数据行
        Object[][] studentData = {
                {1, "张三", 20, new Date(), 95.5},
                {2, "李四", 21, new Date(), 88.0},
                {3, "王五", 19, new Date(), 92.5},
                {4, "赵六", 22, new Date(), 76.5}
        };
        for (int i = 0; i < studentData.length; i++) {
            Row row = sheet.createRow(i + 1); // 数据从第二行开始
            Object[] rowData = studentData[i];
            row.createCell(0).setCellValue((Integer) rowData[0]);
            row.createCell(1).setCellValue((String) rowData[1]);
            row.createCell(2).setCellValue((Integer) rowData[2]);
            // 日期格式化
            Cell dateCell = row.createCell(3);
            dateCell.setCellValue((Date) rowData[3]);
            CellStyle dateStyle = workbook.createCellStyle();
            CreationHelper createHelper = workbook.getCreationHelper();
            dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
            dateCell.setCellStyle(dateStyle);
            row.createCell(4).setCellValue((Double) rowData[4]);
        }
        // 7. 自动调整列宽,使内容能够完整显示
        for (int i = 0; i < headerRow.getPhysicalNumberOfCells(); i++) {
            sheet.autoSizeColumn(i);
        }
        // 8. 将 Workbook 写入文件
        try (FileOutputStream fileOut = new FileOutputStream("students.xlsx")) {
            workbook.write(fileOut);
            System.out.println("Excel 文件 'students.xlsx' 生成成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 9. 关闭 Workbook,释放资源
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

你会得到一个名为 students.xlsx 的文件,内容如下:

ID 姓名 年龄 入学日期 成绩
1 张三 20 2025-10-27 5
2 李四 21 2025-10-27 0
3 王五 19 2025-10-27 5
4 赵六 22 2025-10-27 5

常用操作总结

1. 设置单元格样式

// 1. 创建 CellStyle
CellStyle style = workbook.createCellStyle();
// 2. 设置背景色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 3. 设置边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 4. 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 5. 设置字体
Font font = workbook.createFont();
font.setBold(true);
font.setColor(IndexedColors.BLUE.getIndex());
style.setFont(font);
// 6. 应用样式到单元格
Cell cell = row.createCell(0);
cell.setCellValue("带样式的单元格");
cell.setCellStyle(style);

2. 合并单元格

// 参数:起始行, 结束行, 起始列, 结束列
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2)); // 合并第一行的前三列
// 在合并区域的第一个单元格设置值,其他单元格为空
Cell mergedCell = sheet.getRow(0).getCell(0);
mergedCell.setCellValue("合并的标题");

3. 设置下拉列表

// 1. 创建一个 Sheet 来存放下拉列表的选项 (隐藏这个 Sheet)
Sheet hiddenSheet = workbook.createSheet("DropDownOptions");
// 隐藏 Sheet
workbook.setSheetHidden(workbook.getSheetIndex("DropDownOptions"), true);
// 2. 在隐藏的 Sheet 中添加选项
for (int i = 0; i < options.length; i++) {
    hiddenSheet.createRow(i).createCell(0).setCellValue(options[i]);
}
// 3. 创建名称,指向这些选项
Name namedCell = workbook.createName();
namedCell.setNameName("dropdownList"); // 给这个命名范围起个名字
namedCell.setRefersToFormula("DropDownOptions!$A$1:$A$" + options.length);
// 4. 设置数据验证 (在目标单元格上)
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = helper.createFormulaListConstraint("dropdownList");
CellRangeAddressList addressList = new CellRangeAddressList(1, 10, 0, 0); // 应用于第2到第11行的第1列
DataValidation validation = helper.createValidation(constraint, addressList);
sheet.addValidationData(validation);

大数据量处理 (SXSSF)

当需要生成超过 10 万行的 Excel 时,使用 XSSFWorkbook 会导致内存溢出(OOM),这时必须使用 SXSSF (Streaming Usermodel API)。

核心思想SXSSF 会将一部分数据(默认100行)保存在内存中,其余数据会临时写入磁盘的临时文件,当数据行超过这个阈值时,最早的数据会被写入最终文件并从内存中清除。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class SxssfExample {
    public static void main(String[] args) throws IOException {
        // 1. 创建 SXSSFWorkbook,参数 100 表示在内存中保留的行数
        Workbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("大数据量");
        // 2. 创建标题行
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("行号");
        // 3. 模拟写入 1,000,000 行数据
        for (int i = 1; i <= 1_000_000; i++) {
            Row row = sheet.createRow(i);
            row.createCell(0).setCellValue(i);
            // 每 1000 行刷新一次进度
            if (i % 1000 == 0) {
                System.out.println("已写入 " + i + " 行...");
            }
        }
        // 4. 写入文件
        try (FileOutputStream out = new FileOutputStream("large_data.xlsx")) {
            workbook.write(out);
            System.out.println("大数据量 Excel 文件生成完成!");
        } finally {
            // 5. 关闭 Workbook,非常重要!这会清理临时文件
            ((SXSSFWorkbook) workbook).dispose();
            workbook.close();
        }
    }
}

注意SXSSFWorkbook 的限制:

  • 不能随机访问:一旦数据被写入文件,就不能再修改或读取它。
  • 不能使用 Sheet.autoSizeColumn():因为它需要访问所有数据来计算宽度,这在流式API中是不可能的,你需要手动设置列宽。

最佳实践

  1. 选择正确的 API

    • 小文件 (< 100,000 行): 使用 XSSFWorkbook
    • 大文件 (> 100,000 行): 必须使用 SXSSFWorkbook
    • 需要兼容旧版 Excel: 使用 HSSFWorkbook
  2. 资源管理:始终使用 try-with-resources 语句来包裹 FileOutputStreamWorkbook,确保文件流和资源被正确关闭,防止内存泄漏和文件锁定。

  3. 样式复用:不要为每个单元格都创建一个新的 CellStyle,如果多个单元格需要相同的样式,创建一个 CellStyle 对象,然后重复应用到多个单元格上。

  4. 性能优化:对于 SXSSF,合理设置 windowSize (内存中保留的行数),值越大,性能越好,但内存消耗也越大。

  5. 日期处理:使用 CreationHelper 来创建日期格式,并应用到包含日期的 CellStyle 上,而不是直接在 setCellValue 时格式化字符串。

希望这份详细的指南能帮助你掌握使用 Java POI 生成 Excel 的技能!

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