目录
- Apache POI 简介
- 环境准备
- 核心概念
Workbook(工作簿)Sheet(工作表)Row(行)Cell(单元格)
- 基本导出示例 (
.xlsx格式)- 创建 Excel
- 写入数据
- 设置样式
- 文件下载
- 高级功能示例
- 合并单元格
- 下拉列表
- 超链接
- 图片插入
.xlsvs.xlsx格式选择- 最佳实践与性能优化
- 完整代码示例 (Spring Boot 环境下)
Apache POI 简介
Apache POI 是一个开源的 Java 库,用于操作 Microsoft Office 格式的文件,它提供了一套 API,允许你读取、写入和修改 .xls (Excel 97-2003) 和 .xlsx (Excel 2007 及以上) 格式的文件。

- 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 的对象模型是关键,它就像一个树形结构:
-
Workbook(工作簿): 代表整个 Excel 文件,它是所有 Sheet 的容器。
(图片来源网络,侵删)XSSFWorkbook->.xlsx文件HSSFWorkbook->.xls文件SXSSFWorkbook-> 流式.xlsx文件
-
Sheet(工作表): 代表 Excel 文件中的一个工作表(Sheet1, Sheet2)。- 通过
workbook.createSheet("工作表名")创建。
- 通过
-
Row(行): 代表工作表中的一行,行号从0开始。- 通过
sheet.createRow(行号)创建。
- 通过
-
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);
}
// ... 后续代码 ...
高级功能示例
合并单元格
使用 Sheet 的 addMergedRegion 方法。
// 合并第一行的前四列作为标题
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 是不二之选。
最佳实践与性能优化
-
使用
SXSSF处理大数据:当导出数据量超过 10,000 行时,应立即考虑使用SXSSFWorkbook,它通过将数据写入临时文件的方式,将内存占用降到最低。// 参数 100 表示在内存中保留的行数,超过的会写入磁盘 Workbook workbook = new SXSSFWorkbook(100); // ... 其余代码和 XSSF 一样 ... // 注意:SXSSFWorkbook 在最终写入后,需要调用 .dispose() 清理临时文件 ((SXSSFWorkbook) workbook).dispose();
-
关闭资源:
Workbook和FileOutputStream都是 IO 资源,必须在使用后关闭,推荐使用try-with-resources语句,它能自动关闭资源。 -
避免频繁创建样式:样式对象 (
CellStyle,Font) 比较消耗内存,如果多个单元格需要相同的样式,应该创建一个样式对象,然后重复赋给这些单元格,而不是为每个单元格都创建一个新样式。 -
预计算列宽:在写入大量数据后,再调用
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,是生产环境中非常健壮和高效的导出方案。
