java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java百万数据导出Excel

Java实现百万数据导出Excel的详细指南

作者:天天摸鱼的java工程师

这篇文章主要为大家详细介绍了使用Java语言实现百万数据导出Excel的详细方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

本文分享一个Java开发者从初出茅庐到技术老手的成长历程,聚焦百万级数据导出场景,看如何从OOM崩溃走向优雅解决。

一、新手踩坑:我的第一个导出功能

刚入行那年,我接到第一个独立任务:实现订单数据导出Excel。当时凭着学校学的基础知识,写出了这样的代码:

// 新手版导出代码 - 内存炸弹!
public void exportOrders(HttpServletResponse response) {
    // 1. 一次性加载全量数据
    List<Order> allOrders = orderDao.findAll(); 
    
    // 2. 创建Excel对象(当时还不知道内存代价)
    Workbook workbook = new HSSFWorkbook();
    Sheet sheet = workbook.createSheet("Orders");
    
    // 3. 逐行填充数据
    int rowNum = 0;
    for (Order order : allOrders) {  // 百万次循环
        Row row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue(order.getId());
        row.createCell(1).setCellValue(order.getAmount());
        // ...15+个字段
    }
    
    // 4. 写入响应流
    workbook.write(response.getOutputStream());
}

第一次压测时的灾难现场

Exception in thread "http-nio-8080-exec-3" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
    // 堆栈指向Excel对象创建

二、错误分析:为什么新手代码会OOM

三重内存炸弹

内存消耗估算

组件1万条10万条100万条
订单对象120MB1.2GB12GB
Excel对象(估算)200MB2GB20GB+
总内存320MB3.2GB32GB+

当时我用的测试机只有2G内存...

三、解决之道:流式处理方案

架构演进对比

graph LR
    A[新手方案] -->|全内存| B[OOM崩溃]
    C[优化方案] -->|磁盘缓冲| D[成功导出]
    D -->|内存控制| E[稳定运行]

核心代码改造(基于POI SXSSF)

public void streamExport(HttpServletResponse response) throws Exception {
    // 1. 创建流式工作簿(内存中只保留100行)
    Workbook workbook = new SXSSFWorkbook(100); 
    Sheet sheet = workbook.createSheet("订单数据");
    
    // 2. 写表头
    Row header = sheet.createRow(0);
    header.createCell(0).setCellValue("ID");
    // ...其他表头
    
    // 3. 分页查询+流式写入
    int pageSize = 2000;
    int pageNum = 1;
    int rowIndex = 1; // 数据行起始位置
    
    while (true) {
        // 4. 分页查询(避免全量加载)
        List<Order> page = orderDao.findByPage(pageNum, pageSize);
        if (page.isEmpty()) break;
        
        // 5. 批量写入当前页
        for (Order order : page) {
            Row row = sheet.createRow(rowIndex++);
            row.createCell(0).setCellValue(order.getId());
            // ...其他字段
        }
        
        // 6. 刷新当前页数据到磁盘
        ((SXSSFSheet)sheet).flushRows(page.size());
        
        pageNum++;
    }
    
    // 7. 设置响应头
    response.setContentType("application/vnd.ms-excel");
    response.setHeader("Content-Disposition", "attachment;filename=orders.xlsx");
    
    // 8. 流式输出到客户端
    workbook.write(response.getOutputStream());
    
    // 9. 清理临时文件
    ((SXSSFWorkbook)workbook).dispose();
}

四、关键技术解析

1.SXSSFWorkbook 核心机制

滑动窗口:内存中只保留指定行数(默认100行)

自动刷盘:超过窗口大小的行写入磁盘临时文件

内存对比:传统方式 vs SXSSF

// 传统方式(危险!)
Workbook workbook = new XSSFWorkbook(); 

// 流式处理(安全)
Workbook workbook = new SXSSFWorkbook(100); 

2.分页查询优化技巧

避免深度分页:不要使用limit 1000000,100

推荐方案:基于ID范围的连续分页

SELECT * FROM orders WHERE id > ? ORDER BY id LIMIT 2000

3.内存监控技巧

添加JVM参数观察内存变化:

-XX:+PrintGCDetails -Xloggc:gc.log

五、性能优化实战

样式处理陷阱

// 错误做法:每行创建样式(内存爆炸)
for(Order order : orders) {
    CellStyle style = workbook.createCellStyle();
    row.setCellStyle(style);
}

// 正确做法:样式池复用
CellStyle moneyStyle = workbook.createCellStyle();
moneyStyle.setDataFormat(BuiltinFormats.getBuiltinFormat(4));

// 在需要时直接使用
cell.setCellStyle(moneyStyle);

临时文件管理

// 自定义临时文件位置(避免/tmp爆满)
File tmpDir = new File("/data/tmp");
SXSSFWorkbook workbook = new SXSSFWorkbook(null, 100, true, tmpDir);

写入加速技巧

// 批量设置单元格值(减少方法调用)
Row row = sheet.createRow(0);
Object[] values = {"ID", "金额", "日期"};
for (int i = 0; i < values.length; i++) {
    row.createCell(i).setCellValue(values[i].toString());
}

六、方案效果对比

指标新手方案流式方案
内存占用>3GB (OOM)≈150MB
响应时间无法完成5分钟/百万行
CPU占用频繁Full GC平稳
代码复杂度简单中等
可支持数据量<1万行>1000万行

七、避坑指南:血泪经验

分页查询的坑

// MySQL深度分页性能陷阱
List<Order> list = orderDao.query("SELECT * FROM orders LIMIT 900000,1000");

资源关闭的坑

// 忘记关闭资源导致内存泄漏
Workbook workbook = new SXSSFWorkbook();
// 必须添加finally块关闭

数据类型的坑

// 日期类型特殊处理
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(workbook.createDataFormat().getFormat("yyyy-MM-dd"));
cell.setCellStyle(dateStyle);

八、老鸟的思考

8年Java开发教会我处理海量数据的核心原则:

内存有限性原则

graph LR
内存-->磁盘-->分布式

当内存不够时,合理利用磁盘空间

流式处理三要素

资源管理箴言

"打开的资源要及时关闭,创建的对象要明确生命周期"

最后建议:超过500万行数据建议使用CSV格式或专业ETL工具,Excel毕竟不是数据库!

以上就是Java实现百万数据导出Excel的详细指南的详细内容,更多关于Java百万数据导出Excel的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文