杰瑞科技汇

Java POI导出Excel,如何实现高效与兼容?

Apache POI 是一个开源的 Java 库,用于操作 Microsoft Office 格式文件,包括 Excel,我们将从最基础的用法开始,逐步深入到更复杂和实用的场景。


目录

  1. 准备工作:添加 POI 依赖
  2. 基础导出:.xls 格式 (HSSFWorkbook)
    • 简单示例
    • 代码解析
  3. 推荐使用:.xlsx 格式 (XSSFWorkbook)
    • 简单示例
    • 代码解析
  4. 进阶功能
    • 设置单元格样式(字体、颜色、边框、对齐)
    • 合并单元格
    • 设置列宽
    • 写入大数据量(SXSSFWorkbook 解决内存溢出)
  5. 完整实用示例
  6. 总结与建议

准备工作:添加 POI 依赖

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

<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>
</dependencies>

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

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

基础导出:.xls 格式 (HSSFWorkbook)

.xls 是 Excel 97-2003 的格式,使用 HSSFWorkbook 类来操作。注意:.xls 格式最多支持 65536 行和 256 列,对于现代应用来说通常不够用。

简单示例

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import java.io.FileOutputStream;
import java.io.IOException;
public class XlsExportExample {
    public static void main(String[] args) {
        // 1. 创建一个新的 Excel 工作簿 (对应一个 .xls 文件)
        Workbook workbook = new HSSFWorkbook();
        // 2. 在工作簿中创建一个工作表
        Sheet sheet = workbook.createSheet("员工信息");
        // 3. 创建标题行 (第0行)
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("姓名");
        headerRow.createCell(1).setCellValue("年龄");
        headerRow.createCell(2).setCellValue("部门");
        // 4. 创建数据行
        Row dataRow1 = sheet.createRow(1);
        dataRow1.createCell(0).setCellValue("张三");
        dataRow1.createCell(1).setCellValue(28);
        dataRow1.createCell(2).setCellValue("研发部");
        Row dataRow2 = sheet.createRow(2);
        dataRow2.createCell(0).setCellValue("李四");
        dataRow2.createCell(1).setCellValue(32);
        dataRow2.createCell(2).setCellValue("市场部");
        // 5. 设置列宽 (自动调整)
        for (int i = 0; i < 3; i++) {
            sheet.autoSizeColumn(i);
        }
        // 6. 将工作簿写入文件
        try (FileOutputStream fileOut = new FileOutputStream("D:/temp/employees.xls")) {
            workbook.write(fileOut);
            System.out.println("Excel 文件 (.xls) 导出成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭工作簿,释放资源
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

代码解析

  1. new HSSFWorkbook(): 创建一个 .xls 格式的工作簿对象。
  2. workbook.createSheet("员工信息"): 创建一个名为 "员工信息" 的工作表。
  3. sheet.createRow(0): 在工作表中创建一行,索引从 0 开始。
  4. row.createCell(0): 在行中创建一个单元格,索引从 0 开始。
  5. cell.setCellValue(...): 设置单元格的值,POI 会自动根据数据类型(字符串、数字、布尔值等)进行处理。
  6. sheet.autoSizeColumn(i): 自动调整列宽以适应内容。
  7. new FileOutputStream(...): 指定一个输出流,将 Excel 内容写入文件。
  8. workbook.write(fileOut): 将工作簿对象写入输出流。
  9. workbook.close(): 非常重要,关闭工作簿以释放内存。

推荐使用:.xlsx 格式 (XSSFWorkbook)

.xlsx 是 Office 2007 及以后版本的格式,基于 OpenXML 标准,它使用 XSSFWorkbook 类,支持超过百万行和列,是目前的主流选择。

简单示例

代码结构与 .xls 版本几乎完全相同,只需要将 HSSFWorkbook 替换为 XSSFWorkbook

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class XlsxExportExample {
    public static void main(String[] args) {
        // 1. 创建一个新的 XLSX 格式的工作簿
        Workbook workbook = new XSSFWorkbook();
        // 2. 在工作簿中创建一个工作表
        Sheet sheet = workbook.createSheet("员工信息");
        // 3. 创建标题行
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("姓名");
        headerRow.createCell(1).setCellValue("年龄");
        headerRow.createCell(2).setCellValue("部门");
        // 4. 创建数据行
        Row dataRow1 = sheet.createRow(1);
        dataRow1.createCell(0).setCellValue("王五");
        dataRow1.createCell(1).setCellValue(25);
        dataRow1.createCell(2).setCellValue("人事部");
        Row dataRow2 = sheet.createRow(2);
        dataRow2.createCell(0).setCellValue("赵六");
        dataRow2.createCell(1).setCellValue(30);
        dataRow2.createCell(2).setCellValue("财务部");
        // 5. 设置列宽
        for (int i = 0; i < 3; i++) {
            sheet.autoSizeColumn(i);
        }
        // 6. 写入文件
        try (FileOutputStream fileOut = new FileOutputStream("D:/temp/employees.xlsx")) {
            workbook.write(fileOut);
            System.out.println("Excel 文件 (.xlsx) 导出成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭工作簿
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

对于新项目,请优先使用 .xlsx 格式 (XSSFWorkbook)


进阶功能

1 设置单元格样式

通过 CellStyle 接口,可以设置字体、颜色、边框、对齐方式等。

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

2 合并单元格

使用 Sheet.addMergedRegion() 方法。

// 合并从 (0, 0) 到 (0, 2) 的单元格,即第一行的前三列
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));
// 在合并后的区域的第一个单元格写入数据
headerRow.createCell(0).setCellValue("员工信息总表");

3 设置列宽

// 设置第一列的宽度为 20 个字符宽度
sheet.setColumnWidth(0, 20 * 256);
// 自动调整列宽 (根据内容)
sheet.autoSizeColumn(0);

4 写入大数据量(SXSSFWorkbook

当导出数据量非常大(例如几十万行)时,XSSFWorkbook 会将所有数据都加载到内存中,导致 OutOfMemoryError (内存溢出)。

SXSSFWorkbook 是 POI 提供的 XSSF 的流式版本,它通过 临时文件 的方式来解决这个问题,内存中只保留一定数量的行,其余的行会被写入磁盘。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class BigDataExportExample {
    public static void main(String[] args) {
        // 在内存中保留 100 行,超过的写入临时文件
        // 参数100表示窗口大小,即内存中最多保留的行数
        Workbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("大数据表");
        // 模拟写入 100,000 行数据
        for (int i = 0; i < 100000; i++) {
            Row row = sheet.createRow(i);
            row.createCell(0).setCellValue("ID: " + i);
            row.createCell(1).setCellValue("数据 " + i);
        }
        try (FileOutputStream fileOut = new FileOutputStream("D:/temp/big_data.xlsx")) {
            workbook.write(fileOut);
            System.out.println("大数据 Excel 文件导出成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 清理临时文件 (非常重要!)
            ((SXSSFWorkbook) workbook).dispose();
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:

  • 使用 SXSSFWorkbook 后,不能使用 Sheet.autoSizeColumn(),因为它需要遍历所有行来计算宽度,这在流式模式下是不可能的,你需要手动设置列宽。
  • finally 块中,必须调用 ((SXSSFWorkbook) workbook).dispose() 来删除生成的临时文件。

完整实用示例

结合以上所有功能,导出一个格式化、有标题、有数据的 .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.ArrayList;
import java.util.List;
public class CompleteExcelExport {
    public static void main(String[] args) {
        // 1. 准备数据
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("张三", 28, "研发部", "zhangsan@example.com"));
        employees.add(new Employee("李四", 32, "市场部", "lisi@example.com"));
        employees.add(new Employee("王五", 25, "人事部", "wangwu@example.com"));
        employees.add(new Employee("赵六", 30, "财务部", "zhaoliu@example.com"));
        // 2. 创建工作簿
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("员工信息表");
        // 3. 创建样式
        // 标题样式
        CellStyle titleStyle = createTitleStyle(workbook);
        // 表头样式
        CellStyle headerStyle = createHeaderStyle(workbook);
        // 4. 创建标题行 (合并单元格)
        Row titleRow = sheet.createRow(0);
        titleRow.setHeightInPoints(30); // 设置行高
        Cell titleCell = titleRow.createCell(0);
        titleCell.setCellValue("公司员工信息");
        titleCell.setCellStyle(titleStyle);
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 4)); // 合并6列
        // 5. 创建表头行
        Row headerRow = sheet.createRow(1);
        String[] headers = {"序号", "姓名", "年龄", "部门", "邮箱"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(headerStyle);
        }
        // 6. 写入数据
        int rowNum = 2; // 数据从第3行开始
        for (int i = 0; i < employees.size(); i++) {
            Employee emp = employees.get(i);
            Row row = sheet.createRow(rowNum++);
            row.createCell(0).setCellValue(i + 1);
            row.createCell(1).setCellValue(emp.getName());
            row.createCell(2).setCellValue(emp.getAge());
            row.createCell(3).setCellValue(emp.getDepartment());
            row.createCell(4).setCellValue(emp.getEmail());
        }
        // 7. 设置列宽
        sheet.setColumnWidth(0, 5 * 256);  // 序号
        sheet.setColumnWidth(1, 15 * 256); // 姓名
        sheet.setColumnWidth(2, 8 * 256);  // 年龄
        sheet.setColumnWidth(3, 15 * 256); // 部门
        sheet.setColumnWidth(4, 25 * 256); // 邮箱
        // 8. 写入文件
        try (FileOutputStream fileOut = new FileOutputStream("D:/temp/complete_employees.xlsx")) {
            workbook.write(fileOut);
            System.out.println("完整示例 Excel 文件导出成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    // 创建标题样式
    private static CellStyle createTitleStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setFontName("微软雅黑");
        font.setFontHeightInPoints((short) 18);
        font.setBold(true);
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        return style;
    }
    // 创建表头样式
    private static CellStyle createHeaderStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setFontName("微软雅黑");
        font.setFontHeightInPoints((short) 12);
        font.setBold(true);
        style.setFont(font);
        style.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        return style;
    }
    // 员工实体类
    static class Employee {
        private String name;
        private int age;
        private String department;
        private String email;
        public Employee(String name, int age, String department, String email) {
            this.name = name;
            this.age = age;
            this.department = department;
            this.email = email;
        }
        // Getters...
        public String getName() { return name; }
        public int getAge() { return age; }
        public String getDepartment() { return department; }
        public String getEmail() { return email; }
    }
}

总结与建议

功能 适用场景 备注
基础格式 HSSFWorkbook 兼容旧版 Excel (.xls) 最多 65536 行,已不推荐
现代格式 XSSFWorkbook Office 2007+ (.xlsx) 支持大数据,内存占用高
大数据流式 SXSSFWorkbook 导出海量数据 (.xlsx) 内存友好,必须手动设置列宽,用后需 dispose()

最佳实践:

  1. 优先选择 .xlsx:除非有特殊的兼容性要求,否则始终使用 XSSFWorkbookSXSSFWorkbook
  2. 大数据量用 SXSSFWorkbook:如果预估数据量超过 5 万行,直接使用 SXSSFWorkbook,并设置一个合适的窗口大小(如 100 或 500)。
  3. 善用样式:提前创建好 CellStyleFont 对象,然后重复应用到不同的单元格上,避免重复创建,提高性能。
  4. 资源管理:始终使用 try-with-resources 或在 finally 块中确保 WorkbookFileOutputStream 被正确关闭,对于 SXSSFWorkbook,不要忘记调用 dispose()
  5. 性能考虑:在循环中创建对象(如 Row, Cell, CellStyle)是常见的性能瓶颈,尽量在循环外创建样式和字体,在循环内重复使用。
分享:
扫描分享到社交APP
上一篇
下一篇