本文目录导读:

- 目录导读
- 为什么Java开发者需要掌握Excel生成?
- 主流方案对比:Apache POI vs Alibaba EasyExcel vs JXL
- 实战案例一:基于POI生成带样式和公式的Excel
- 实战案例二:百万级数据用EasyExcel流式写入
- 常见性能陷阱与内存优化策略
- FAQ:高频问题深度解析
- 总结与选型建议
Java生成Excel终极指南:从POI到EasyExcel的实战案例与性能对比
目录导读
- 为什么Java开发者需要掌握Excel生成?
- 主流方案对比:Apache POI vs Alibaba EasyExcel vs JXL
- 实战案例一:基于POI生成带样式和公式的Excel
- 实战案例二:百万级数据用EasyExcel流式写入
- 常见性能陷阱与内存优化策略
- FAQ:高频问题深度解析
- 总结与选型建议
为什么Java开发者需要掌握Excel生成?
在企业级应用中,报表导出、数据备份、批量导入模板等场景几乎离不开Excel,Java作为后端主流语言,生成Excel的能力直接决定了系统的交付质量,搜索引擎上关于“Java生成Excel案例”的诉求量常年位居技术榜单前列,而多数教程只停留在“能跑”阶段,缺乏对性能与内存的深度剖析,本文结合Apache POI与EasyExcel两大生态,通过可复现的代码案例,帮你避开常见的坑。
主流方案对比:Apache POI vs Alibaba EasyExcel vs JXL
| 方案 | 内存占用 | 学习曲线 | 功能丰富度 | 适用场景 |
|---|---|---|---|---|
| Apache POI (XSSF) | 高(DOM模型) | 中 | 复杂样式、公式、图表 | |
| POI + SXSSF | 中(流式写入) | 中 | 大文件写入 | |
| Alibaba EasyExcel | 低(SAX模式) | 低 | 大数据量读写 | |
| JXL (已停更) | 中 | 低 | 遗产项目维护 |
关键结论:若需导出超过5万行数据,POI的XSSFWorkbook会引发OOM,必须改用SXSSF或EasyExcel。
实战案例一:基于POI生成带样式和公式的Excel
// 引入依赖:org.apache.poi:poi-ooxml:5.2.3
public static void createStyledExcel(String path) throws IOException {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("销售报表");
// 创建标题行样式
XSSFCellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setFont(createBoldFont(workbook));
// 创建数据行
String[] headers = {"月份", "销售额", "成本", "利润"};
Row row0 = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = row0.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 填充数据(示例3行)
Object[][] data = {
{"一月", 12000, 8000, null},
{"二月", 15000, 9000, null},
{"三月", 18000, 11000, null}
};
for (int i = 0; i < data.length; i++) {
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue((String) data[i][0]);
row.createCell(1).setCellValue((Integer) data[i][1]);
row.createCell(2).setCellValue((Integer) data[i][2]);
// 设置公式:利润 = 销售额 - 成本
Cell formulaCell = row.createCell(3);
formulaCell.setCellFormula(String.format("B%d-C%d", i + 2, i + 2));
}
// 自动调整列宽
sheet.autoSizeColumn(0);
sheet.autoSizeColumn(1);
try (FileOutputStream fos = new FileOutputStream(path)) {
workbook.write(fos);
}
}
}
注意:autoSizeColumn在数据量大时非常消耗性能,建议对固定列宽手动设置sheet.setColumnWidth(index, width * 256)。
实战案例二:百万级数据用EasyExcel流式写入
对于超过10万行的场景,EasyExcel采用SAX解析模式,内存占用仅几十KB,以下为最佳实践:
// 依赖:com.alibaba:easyexcel:3.3.2
@Data
public class OrderData {
@ExcelProperty("订单号")
private String orderId;
@ExcelProperty("金额")
private BigDecimal amount;
@ExcelProperty("状态")
private String status;
}
public static void massiveWrite(String path) {
// 先写表头
EasyExcel.write(path, OrderData.class)
.sheet("订单")
.doWrite(() -> {
// 分页查询数据库,每次返回1万条
for (int page = 0; page < 100; page++) {
List<OrderData> batch = queryFromDB(page, 10000);
if (batch.isEmpty()) {
break;
}
return batch; // EasyExcel会自动分批次写入
}
return Collections.emptyList();
});
}
核心原理:EasyExcel将数据逐行转换为字符串后直接写入临时文件,最后再合并,避免整个工作簿驻留内存。
常见性能陷阱与内存优化策略
- 陷阱1:使用
XSSFWorkbook加载模板再填充 —— 模板文件若超过5MB,建议改用OPCPackage.open(InputStream)读取并转为SXSSFWorkbook。 - 陷阱2:频繁创建
Font和style对象 —— 每个单元格如果单独new样式,会产生大量Java对象,正确做法是复用预定义的样式常量。 - 陷阱3:忘记设置
SXSSFWorkbook的滑动窗口大小 —— 默认100行,如果单个Sheet超过5万行,建议设置为500,并在写完后调用dispose()释放临时文件。 - 优化建议:使用
@ExcelIgnore注解忽略不需要的字段,用EasyExcel.write().autoCloseStream(false)配合ByteArrayOutputStream做网络传输。
FAQ:高频问题深度解析
Q1:POI生成Excel后乱码怎么办?
A:检查单元格类型是否设置正确,如果是中文,用cell.setCellValue(String),同时确保响应头设置Content-Disposition为attachment; filename*=UTF-8''${encodeURIComponent(fileName)}。
Q2:如何导出多Sheet的工作簿?
A:EasyExcel支持excelWriter.write(List, sheetName)多次调用,但务必每次传入不同的Sheet名称,POI则直接创建多个XSSFSheet。
Q3:生成Excel后如何转化为PDF打印?
A:推荐先用POI生成Excel,再用LibreOffice无头模式转换(soffice --headless --convert-to pdf),注意处理中文字体配置。
Q4:大数据量导出时如何避免OOM?
A:终极方案是走异步任务(如Spring @Async),返回下载链接,前端轮询完成后展示,同时配合SXSSFWorkbook(windowSize=500, compressTempFiles=true)。
Q5:EasyExcel支持模板填充吗(类似POI的fill)?
A:支持,使用.withTemplate(fillTemplateFile()),但模板中必须用{变量名}占位,注意填充复杂列表时,需要在模板第二行开始放数据。
总结与选型建议
| 需求维度 | 推荐方案 |
|---|---|
| <1万行,样式复杂(合并单元格、图表) | Apache POI (XSSF) |
| 1万~10万行,列较多 | POI + SXSSF |
| >10万行,列结构固定 | EasyExcel |
| 需要读Excel做导入 | EasyExcel(内存安全) |
最后提醒:无论选择哪种方案,务必在生产环境压测内存,本文的案例代码均可直接运行,建议你结合搜索引擎上真实业务错误报告(如“Java生成Excel内存溢出”),二次深挖底层源码,才能写出真正健壮的导出服务。
如果你在项目中遇到过其他奇葩问题,欢迎在评论区留言,我会挑选高频问题继续深挖。