杰瑞科技汇

Java POI导出Excel时如何设置样式?

目录

  1. Apache POI 简介
  2. 环境准备
  3. 核心概念
    • Workbook (工作簿)
    • Sheet (工作表)
    • Row (行)
    • Cell (单元格)
  4. 基本导出示例 (.xlsx 格式)
    • 创建 Excel
    • 写入数据
    • 设置样式
    • 文件下载
  5. 高级功能示例
    • 合并单元格
    • 下拉列表
    • 超链接
    • 图片插入
  6. .xls vs .xlsx 格式选择
  7. 最佳实践与性能优化
  8. 完整代码示例 (Spring Boot 环境下)

Apache POI 简介

Apache POI 是一个开源的 Java 库,用于操作 Microsoft Office 格式的文件,它提供了一套 API,允许你读取、写入和修改 .xls (Excel 97-2003) 和 .xlsx (Excel 2007 及以上) 格式的文件。

Java POI导出Excel时如何设置样式?-图1
(图片来源网络,侵删)
  • HSSF: 操作 .xls 格式,纯 Java 实现,不依赖外部库。
  • XSSF: 操作 .xlsx 格式,纯 Java 实现,但非常消耗内存。
  • SXSSF: 操作 .xlsx 格式,是 XSSF 的一种流式实现,内存占用极低,适合处理大数据量(百万行级别)的导出。

强烈建议:

  • 对于新项目,优先使用 .xlsx 格式。
  • 对于大数据量导出,必须使用 SXSSF

环境准备

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

<!-- poi 核心库 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.3</version> <!-- 请使用最新版本 -->
</dependency>
<!-- 用于操作 .xlsx 格式 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>
<!-- 用于操作 .xlsx 格式的大数据量流式API (非常重要) -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>5.2.3</version>
</dependency>

核心概念

理解 POI 的对象模型是关键,它就像一个树形结构:

  1. Workbook (工作簿): 代表整个 Excel 文件,它是所有 Sheet 的容器。

    Java POI导出Excel时如何设置样式?-图2
    (图片来源网络,侵删)
    • XSSFWorkbook -> .xlsx 文件
    • HSSFWorkbook -> .xls 文件
    • SXSSFWorkbook -> 流式 .xlsx 文件
  2. Sheet (工作表): 代表 Excel 文件中的一个工作表(Sheet1, Sheet2)。

    • 通过 workbook.createSheet("工作表名") 创建。
  3. Row (行): 代表工作表中的一行,行号从 0 开始。

    • 通过 sheet.createRow(行号) 创建。
  4. Cell (单元格): 代表行中的一个单元格。

    • 通过 row.createCell(列号) 创建。
    • 单元格需要设置数据类型,如 CellType.STRING, CellType.NUMERIC 等。

基本导出示例 (.xlsx 格式)

这是一个最简单的控制台导出示例,将数据写入一个本地文件。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class BasicExcelExport {
    public static void main(String[] args) {
        // 1. 创建一个工作簿 (Workbook)
        // XSSFWorkbook 用于 .xlsx 格式
        Workbook workbook = new XSSFWorkbook();
        // 2. 创建一个工作表 (Sheet)
        Sheet sheet = workbook.createSheet("员工信息");
        // 3. 创建表头行 (Row)
        Row headerRow = sheet.createRow(0);
        String[] headers = {"ID", "姓名", "年龄", "部门"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }
        // 4. 创建数据行 (Row)
        Object[][] data = {
                {1, "张三", 28, "技术部"},
                {2, "李四", 32, "市场部"},
                {3, "王五", 24, "人事部"}
        };
        for (int i = 0; i < data.length; i++) {
            Row dataRow = sheet.createRow(i + 1); // 从第二行开始
            for (int j = 0; j < data[i].length; j++) {
                Cell cell = dataRow.createCell(j);
                // 设置单元格数据类型和值
                if (data[i][j] instanceof String) {
                    cell.setCellValue((String) data[i][j]);
                } else if (data[i][j] instanceof Integer) {
                    cell.setCellValue((Integer) data[i][j]);
                }
                // 可以根据需要添加更多数据类型的判断
            }
        }
        // 5. 写入文件
        try (FileOutputStream outputStream = new FileOutputStream("D:/temp/员工信息.xlsx")) {
            workbook.write(outputStream);
            System.out.println("Excel 文件生成成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭工作簿,释放资源
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

设置样式

// ... 在创建表头行后添加 ...
// 创建一个字体样式
Font headerFont = workbook.createFont();
headerFont.setFontHeightInPoints((short) 14);
headerFont.setBold(true);
// 创建一个单元格样式
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 添加边框
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
// 将样式应用到表头单元格
for (int i = 0; i < headers.length; i++) {
    Cell cell = headerRow.createCell(i);
    cell.setCellValue(headers[i]);
    cell.setCellStyle(headerStyle);
}
// ... 后续代码 ...

高级功能示例

合并单元格

使用 SheetaddMergedRegion 方法。

// 合并第一行的前四列作为标题
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
行Row = sheet.createRow(0);Cell = titleRow.createCell(0);Cell.setCellValue("公司员工总表");行应用一个居中的样式
CellStyle centeredStyle = workbook.createCellStyle();
centeredStyle.setAlignment(HorizontalAlignment.CENTER);Cell.setCellStyle(centeredStyle);

下拉列表 (数据验证)

// 创建一个新的工作表来存放下拉列表的数据源
Sheet hiddenSheet = workbook.createSheet("hidden");
// 在第一列写入下拉选项
hiddenSheet.createRow(0).createCell(0).setCellValue("技术部");
hiddenSheet.createRow(1).createCell(0).setCellValue("市场部");
hiddenSheet.createRow(2).createCell(0).setCellValue("人事部");
// 定义名称引用
Name namedCell = workbook.createName();
namedCell.setNameName("departments");
// 引用隐藏工作表的数据区域
namedCell.setRefersToFormula("hidden!$A$1:$A$3");
// 在主工作表的部门列创建下拉列表
CellRangeAddressList addressList = new CellRangeAddressList(1, 100, 1, 1); // 从第2行到第101行,第2列
DataValidationHelper validationHelper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = validationHelper.createFormulaListConstraint("departments");
DataValidation validation = validationHelper.createValidation(constraint, addressList);
sheet.addValidationData(validation);

超链接

Row linkRow = sheet.createRow(5);
Cell linkCell = linkRow.createCell(0);
linkCell.setCellValue("点击访问百度");
CreationHelper createHelper = workbook.getCreationHelper();
Hyperlink link = createHelper.createHyperlink(HyperlinkType.URL);
link.setAddress("https://www.baidu.com");
linkCell.setHyperlink(link);
// 设置超链接样式,使其看起来像链接
CellStyle linkStyle = workbook.createCellStyle();
Font linkFont = workbook.createFont();
linkFont.setUnderline(Font.U_SINGLE);
linkFont.setColor(IndexedColors.BLUE.getIndex());
linkStyle.setFont(linkFont);
linkCell.setCellStyle(linkStyle);

图片插入

// 读取图片文件
byte[] imageBytes = Files.readAllBytes(Paths.get("D:/temp/logo.png"));
// 将图片添加到工作簿的绘图区
int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG);
// 获取绘图管理器
Drawing<?> drawing = sheet.createDrawingPatriarch();
// 创建锚点 (图片位置和大小)
ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 0, 1, 1); // 左上角在(0,0),右下角在(1,1)
// 创建图片
Picture pict = drawing.createPicture(anchor, pictureIdx);
pict.resize(); // 自动调整图片大小以适应单元格

.xls vs .xlsx 格式选择

特性 .xls (HSSF) .xlsx (XSSF/SXSSF)
文件格式 Excel 97-2003 Excel 2007 及以上
扩展名 .xls .xlsx
最大行数 65,536 行 1,048,576 行
最大列数 256 列 (IV) 16,384 列 (XFD)
内存占用 较低 XSSF 很高,SXSSF 极低
大数据量 不适合 必须使用 SXSSF
兼容性 所有 Excel 版本 新版 Excel (2007+)

除非你有特殊需求(如必须兼容非常古老的 Excel 版本),否则始终优先选择 .xlsx 格式,对于大数据量,SXSSF 是不二之选


最佳实践与性能优化

  1. 使用 SXSSF 处理大数据:当导出数据量超过 10,000 行时,应立即考虑使用 SXSSFWorkbook,它通过将数据写入临时文件的方式,将内存占用降到最低。

    // 参数 100 表示在内存中保留的行数,超过的会写入磁盘
    Workbook workbook = new SXSSFWorkbook(100);
    // ... 其余代码和 XSSF 一样 ...
    // 注意:SXSSFWorkbook 在最终写入后,需要调用 .dispose() 清理临时文件
    ((SXSSFWorkbook) workbook).dispose();
  2. 关闭资源WorkbookFileOutputStream 都是 IO 资源,必须在使用后关闭,推荐使用 try-with-resources 语句,它能自动关闭资源。

  3. 避免频繁创建样式:样式对象 (CellStyle, Font) 比较消耗内存,如果多个单元格需要相同的样式,应该创建一个样式对象,然后重复赋给这些单元格,而不是为每个单元格都创建一个新样式。

  4. 预计算列宽:在写入大量数据后,再调用 autoSizeColumn() 会非常耗时,可以在写入数据前或写入过程中,根据预估的文本长度手动设置列宽。


完整代码示例 (Spring Boot 环境下)

这是在 Web 应用中最常见的场景:用户点击按钮,浏览器下载一个 Excel 文件。

Controller 层

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Controller
public class ExcelExportController {
    @GetMapping("/export/users")
    public void exportUsers(HttpServletResponse response) throws IOException {
        // 1. 设置响应头,告诉浏览器这是一个文件下载
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=user_list.xlsx");
        // 2. 创建 SXSSFWorkbook,用于大数据量导出
        // 100 是内存中缓存的行数,超过的会写入临时文件
        Workbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("用户列表");
        // 3. 创建表头
        Row headerRow = sheet.createRow(0);
        String[] headers = {"用户ID", "用户名", "邮箱", "创建时间"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }
        // 4. 模拟数据
        List<User> users = generateMockUsers(10000); // 假设有10000条数据
        // 5. 写入数据
        for (int i = 0; i < users.size(); i++) {
            Row row = sheet.createRow(i + 1);
            User user = users.get(i);
            row.createCell(0).setCellValue(user.getId());
            row.createCell(1).setCellValue(user.getUsername());
            row.createCell(2).setCellValue(user.getEmail());
            // 日期需要特殊处理
            CreationHelper createHelper = workbook.getCreationHelper();
            Cell dateCell = row.createCell(3);
            dateCell.setCellValue(createHelper.createRichTextString(user.getCreateTime().toString()));
        }
        // 6. 使用 StreamingResponseBody 将文件流式输出到响应中
        // 这样可以避免在服务器端生成巨大的临时文件
        try (workbook) { // try-with-resources 确保 workbook 被关闭
            workbook.write(response.getOutputStream());
        } catch (IOException e) {
            // 处理异常
            e.printStackTrace();
        }
    }
    private List<User> generateMockUsers(int count) {
        // ... 省略模拟数据生成逻辑 ...
        return new ArrayList<>();
    }
}
// 假设的 User 类
class User {
    private long id;
    private String username;
    private String email;
    private java.util.Date createTime;
    // getters and setters...
}

这个例子结合了 Spring Boot 的 HttpServletResponse 和 POI 的 SXSSFWorkbook,是生产环境中非常健壮和高效的导出方案。

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