目录
- Apache POI 简介
- 准备工作:添加 Maven 依赖
- 基础示例:导出
.xls(Excel 97-2003) 格式 - 推荐实践:导出
.xlsx(Excel 2007+) 格式 - 高级功能
- 设置单元格样式(字体、颜色、边框、对齐方式)
- 合并单元格
- 设置列宽和行高
- 使用公式
- 处理日期
- 大数据量导出(SXSSF)
- 完整示例代码
- 最佳实践和注意事项
Apache POI 简介
Apache POI (Poor Obfuscation Implementation) 是一个开源的 Java 库,用于操作 Microsoft Office 格式的文件,它提供了强大的 API 来创建、读取和修改 .xls (旧版 Excel) 和 .xlsx (新版 Excel) 文件。

对于导出 Excel,POI 主要提供了三种实现:
- HSSF: 用于操作
.xls格式,它将所有数据都存储在内存中,如果数据量很大(例如超过 10,000 行),可能会导致内存溢出(OutOfMemoryError)。 - XSSF: 用于操作
.xlsx格式,同样将所有数据存储在内存中,大数据量时也会有内存问题。 - SXSSF: (推荐用于大数据量) 也用于操作
.xlsx格式,它通过“滑动窗口”机制将部分数据写入临时文件,从而极大地减少了内存消耗,非常适合导出海量数据。
准备工作:添加 Maven 依赖
在你的 pom.xml 文件中添加 POI 的依赖,为了同时支持旧版和新版格式,建议添加 poi 和 poi-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 类似。

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;
}
}
最佳实践和注意事项
-
版本选择:
- 优先使用
.xlsx格式 (XSSF/SXSSF)。 - 数据量小(< 1万行)用
XSSFWorkbook。 - 数据量大(> 1万行)必须用
SXSSFWorkbook。
- 优先使用
-
资源管理:
- 务必使用
try-with-resources语句来包裹FileOutputStream,确保文件流被正确关闭。 - 在使用
SXSSFWorkbook后,务必调用dispose()方法删除临时文件。
- 务必使用
-
样式复用:
- 避免在循环中重复创建
CellStyle和Font,应该在循环外创建好,然后在循环中应用到单元格。
- 避免在循环中重复创建
-
性能优化:
- 对于
SXSSFWorkbook,合理设置rowAccessWindowSize,太小会增加磁盘IO,太大会占用过多内存,100-1000 通常是不错的选择。 - 在写入大量数据前,可以先关闭
Sheet的自动计算和自动筛选功能(如果不需要)。
- 对于
-
日期处理:
记得为日期类型的单元格应用日期格式,否则会显示成一长串数字。
-
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(); }
-
