杰瑞科技汇

Java Excel导出POI如何实现?

目录

  1. Apache POI 简介
  2. 准备工作:添加 Maven 依赖
  3. 基础示例:导出 .xls (Excel 97-2003) 格式
  4. 推荐实践:导出 .xlsx (Excel 2007+) 格式
  5. 高级功能
    • 设置单元格样式(字体、颜色、边框、对齐方式)
    • 合并单元格
    • 设置列宽和行高
    • 使用公式
    • 处理日期
  6. 大数据量导出(SXSSF)
  7. 完整示例代码
  8. 最佳实践和注意事项

Apache POI 简介

Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,用于操作 Microsoft Office 格式的文件,它提供了强大的 API 来创建、读取和修改 .xls (旧版 Excel) 和 .xlsx (新版 Excel) 文件。

Java Excel导出POI如何实现?-图1
(图片来源网络,侵删)

对于导出 Excel,POI 主要提供了三种实现:

  • HSSF: 用于操作 .xls 格式,它将所有数据都存储在内存中,如果数据量很大(例如超过 10,000 行),可能会导致内存溢出(OutOfMemoryError)。
  • XSSF: 用于操作 .xlsx 格式,同样将所有数据存储在内存中,大数据量时也会有内存问题。
  • SXSSF: (推荐用于大数据量) 也用于操作 .xlsx 格式,它通过“滑动窗口”机制将部分数据写入临时文件,从而极大地减少了内存消耗,非常适合导出海量数据。

准备工作:添加 Maven 依赖

在你的 pom.xml 文件中添加 POI 的依赖,为了同时支持旧版和新版格式,建议添加 poipoi-ooxml

<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>
    <!-- (可选) 用于处理 XML 标签,避免日志警告 -->
    <dependency>
        <groupId>org.apache.xmlbeans</groupId>
        <artifactId>xmlbeans</artifactId>
        <version>5.1.1</version>
    </dependency>
    <!-- (可选) 用于处理 OOXML 中的图片等资源 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

基础示例:导出 .xls 格式

这个示例演示了如何创建一个简单的 .xls 文件并写入数据。

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class BasicXlsExport {
    public static void main(String[] args) {
        // 1. 创建一个新的 HSSFWorkbook 对象,代表一个 Excel 文件
        Workbook workbook = new HSSFWorkbook();
        // 2. 创建一个工作表,名为 "Sheet1"
        Sheet sheet = workbook.createSheet("Sheet1");
        // 3. 创建表头行(第 0 行)
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("ID");
        headerRow.createCell(1).setCellValue("姓名");
        headerRow.createCell(2).setCellValue("年龄");
        // 4. 创建数据行
        Row dataRow1 = sheet.createRow(1);
        dataRow1.createCell(0).setCellValue(1);
        dataRow1.createCell(1).setCellValue("张三");
        dataRow1.createCell(2).setCellValue(25);
        Row dataRow2 = sheet.createRow(2);
        dataRow2.createCell(0).setCellValue(2);
        dataRow2.createCell(1).setCellValue("李四");
        dataRow2.createCell(2).setCellValue(30);
        // 5. 写入文件
        try (FileOutputStream fileOut = new FileOutputStream("D:/temp/basic_export.xls")) {
            workbook.write(fileOut);
            System.out.println("basic_export.xls 文件已成功生成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭工作簿,释放资源
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

推荐实践:导出 .xlsx 格式

.xlsx 是目前的主流格式,推荐使用 XSSFWorkbook,代码结构与 HSSF 类似。

Java Excel导出POI如何实现?-图2
(图片来源网络,侵删)
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import java.io.FileOutputStream;
import java.io.IOException;
public class BasicXlsxExport {
    public static void main(String[] args) {
        // 1. 创建一个新的 XSSFWorkbook 对象
        Workbook workbook = new XSSFWorkbook();
        // 2. 创建工作表
        Sheet sheet = workbook.createSheet("员工信息");
        // 3. 创建表头
        Row headerRow = sheet.createRow(0);
        String[] headers = {"ID", "姓名", "部门", "入职日期"};
        for (int i = 0; i < headers.length; i++) {
            headerRow.createCell(i).setCellValue(headers[i]);
        }
        // 4. 创建数据
        Object[][] data = {
                {1, "王五", "技术部", new java.util.Date()},
                {2, "赵六", "市场部", new java.util.Date()},
                {3, "钱七", "财务部", new java.util.Date()}
        };
        int rowNum = 1;
        for (Object[] rowData : data) {
            Row row = sheet.createRow(rowNum++);
            for (int i = 0; i < rowData.length; i++) {
                // 处理日期类型
                if (rowData[i] instanceof java.util.Date) {
                    row.createCell(i).setCellValue((java.util.Date) rowData[i]);
                } else {
                    row.createCell(i).setCellValue(String.valueOf(rowData[i]));
                }
            }
        }
        // 5. 写入文件
        try (FileOutputStream fileOut = new FileOutputStream("D:/temp/basic_export.xlsx")) {
            workbook.write(fileOut);
            System.out.println("basic_export.xlsx 文件已成功生成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (workbook != null) {
                    workbook.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

高级功能

1 设置单元格样式

样式可以统一创建并复用,提高效率。

// 创建样式
CellStyle headerStyle = workbook.createCellStyle();
// 设置背景色
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置字体
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
// 设置居中
headerStyle.setAlignment(HorizontalAlignment.CENTER);
// 应用样式到表头
for (int i = 0; i < headers.length; i++) {
    headerRow.getCell(i).setCellStyle(headerStyle);
}

2 合并单元格

使用 Sheet 对象的 addMergedRegion 方法。

// 合并第一行的第 0 列到第 3 列
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
// 在合并后的单元格中写入内容Row = sheet.createRow(0);Row.createCell(0).setCellValue("员工信息列表");Row.getCell(0).setCellStyle(titleStyle); // 应用一个标题样式

3 设置列宽和行高

// 设置列宽 (列索引, 宽度) 宽度是 1/256 个字符宽度
sheet.setColumnWidth(0, 5 * 256); // ID 列
sheet.setColumnWidth(1, 10 * 256); // 姓名列
// 设置行高 (行索引, 高度) 高度是 1/20 个点
sheet.getRow(0).setHeightInPoints(20f);

4 使用公式

// 假设 A1=10, B1=20, 在 C1 单元格中写入求和公式
Row formulaRow = sheet.createRow(0);
formulaRow.createCell(0).setCellValue(10);
formulaRow.createCell(1).setCellValue(20);
formulaRow.createCell(2).setCellFormula("SUM(A1:B1)");

5 处理日期

默认情况下,POI 会将日期存储为一个数字(距离 1900/1/1 的天数),你需要为其应用 yyyy-mm-dd 格式的样式才能正确显示。

// 创建日期格式
CellStyle dateStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
// 写入日期并应用样式
Row dateRow = sheet.createRow(1);
Cell dateCell = dateRow.createCell(0);
dateCell.setCellValue(new java.util.Date());
dateCell.setCellStyle(dateStyle);

大数据量导出(SXSSF)

当数据量超过 10 万行时,必须使用 SXSSFWorkbook

核心思想SXSSFWorkbook 内部有一个 rowAccessWindowSize(窗口大小,默认为100),它只保留这个数量的行在内存中,当超过这个数量时,最早的数据会被写入临时文件,从而释放内存。

import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
public class LargeDataExport {
    public static void main(String[] args) throws IOException {
        // 1. 创建 SXSSFWorkbook,窗口大小设置为 100
        // -1 表示不限制在内存中的行数,全部写入磁盘
        // 通常设置为 100 到 1000 之间
        Workbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("大数据报表");
        // 创建表头
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("序号");
        headerRow.createCell(1).setCellValue("随机数");
        // 2. 写入大量数据 (100,000 行)
        System.out.println("开始生成数据...");
        Random random = new Random();
        for (int i = 1; i <= 100_000; i++) {
            Row row = sheet.createRow(i);
            row.createCell(0).setCellValue(i);
            row.createCell(1).setCellValue(random.nextDouble() * 1000);
            // 每一万行打印一次进度
            if (i % 10000 == 0) {
                System.out.println("已生成 " + i + " 行数据");
            }
        }
        System.out.println("数据生成完成!");
        // 3. 写入文件
        try (FileOutputStream fileOut = new FileOutputStream("D:/temp/large_data_export.xlsx")) {
            workbook.write(fileOut);
            System.out.println("large_data_export.xlsx 文件已成功生成!");
        } finally {
            // 4. 清理临时文件!非常重要!
            ((SXSSFWorkbook) workbook).dispose();
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注意SXSSFWorkbook 的一些限制:

  • 不支持 Sheet.clone()
  • 不支持 FormulaEvaluator 来重新计算公式。
  • 在写入完成后,必须调用 dispose() 方法来删除临时文件

完整示例代码(结合高级功能)

这是一个结合了样式、日期、大数据量导出思想的完整工具类示例。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
public class ExcelExportUtil {
    /**
     * 导出 Excel (大数据量)
     * @param headers 表头
     * @param dataList 数据列表
     * @param fileName 文件名
     */
    public static void exportLargeExcel(String[] headers, List<Object[]> dataList, String fileName) {
        // 1. 创建 SXSSFWorkbook
        Workbook workbook = new SXSSFWorkbook(100); // 窗口大小100
        // 2. 创建样式
        CellStyle headerStyle = createHeaderStyle(workbook);
        CellStyle dateStyle = createDateStyle(workbook);
        // 3. 创建工作表和表头
        Sheet sheet = workbook.createSheet("数据");
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < headers.length; i++) {
            headerRow.createCell(i).setCellValue(headers[i]);
            headerRow.getCell(i).setCellStyle(headerStyle);
        }
        // 4. 写入数据
        int rowNum = 1;
        for (Object[] rowData : dataList) {
            Row row = sheet.createRow(rowNum++);
            for (int i = 0; i < rowData.length; i++) {
                Cell cell = row.createCell(i);
                Object value = rowData[i];
                if (value instanceof String) {
                    cell.setCellValue((String) value);
                } else if (value instanceof Integer) {
                    cell.setCellValue((Integer) value);
                } else if (value instanceof Double) {
                    cell.setCellValue((Double) value);
                } else if (value instanceof Date) {
                    cell.setCellValue((Date) value);
                    cell.setCellStyle(dateStyle);
                } else if (value instanceof Boolean) {
                    cell.setCellValue((Boolean) value);
                }
            }
        }
        // 5. 调整列宽
        for (int i = 0; i < headers.length; i++) {
            sheet.autoSizeColumn(i);
        }
        // 6. 写入文件
        try (FileOutputStream fileOut = new FileOutputStream(fileName)) {
            workbook.write(fileOut);
            System.out.println(fileName + " 文件已成功生成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 7. 清理资源
            ((SXSSFWorkbook) workbook).dispose();
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private static CellStyle createHeaderStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setFontHeightInPoints((short) 12);
        font.setBold(true);
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        return style;
    }
    private static CellStyle createDateStyle(Workbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setDataFormat(workbook.createDataFormat().getFormat("yyyy-mm-dd hh:mm:ss"));
        return style;
    }
}

最佳实践和注意事项

  1. 版本选择

    • 优先使用 .xlsx 格式 (XSSF / SXSSF)。
    • 数据量小(< 1万行)用 XSSFWorkbook
    • 数据量大(> 1万行)必须SXSSFWorkbook
  2. 资源管理

    • 务必使用 try-with-resources 语句来包裹 FileOutputStream,确保文件流被正确关闭。
    • 在使用 SXSSFWorkbook 后,务必调用 dispose() 方法删除临时文件。
  3. 样式复用

    • 避免在循环中重复创建 CellStyleFont,应该在循环外创建好,然后在循环中应用到单元格。
  4. 性能优化

    • 对于 SXSSFWorkbook,合理设置 rowAccessWindowSize,太小会增加磁盘IO,太大会占用过多内存,100-1000 通常是不错的选择。
    • 在写入大量数据前,可以先关闭 Sheet 的自动计算和自动筛选功能(如果不需要)。
  5. 日期处理

    记得为日期类型的单元格应用日期格式,否则会显示成一长串数字。

  6. Web 环境下导出

    • 在 Web 应用(如 Spring Boot)中导出时,通常是通过 Controller 返回一个 ResponseEntity,并将 Workbook 写入到 HttpServletResponse 的输出流中。

      @GetMapping("/export")
      public void exportExcel(HttpServletResponse response) throws IOException {
      // ... 创建 workbook ...
      String fileName = "report.xlsx";
      response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
      response.setHeader("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1"));
      workbook.write(response.getOutputStream());
      workbook.close();
      }
分享:
扫描分享到社交APP
上一篇
下一篇