欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實(shí)現(xiàn)百萬(wàn)數(shù)據(jù)導(dǎo)出Excel的詳細(xì)指南

 更新時(shí)間:2025年07月16日 08:26:07   作者:天天摸魚(yú)的java工程師  
這篇文章主要為大家詳細(xì)介紹了使用Java語(yǔ)言實(shí)現(xiàn)百萬(wàn)數(shù)據(jù)導(dǎo)出Excel的詳細(xì)方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

本文分享一個(gè)Java開(kāi)發(fā)者從初出茅廬到技術(shù)老手的成長(zhǎng)歷程,聚焦百萬(wàn)級(jí)數(shù)據(jù)導(dǎo)出場(chǎng)景,看如何從OOM崩潰走向優(yōu)雅解決。

一、新手踩坑:我的第一個(gè)導(dǎo)出功能

剛?cè)胄心悄?,我接到第一個(gè)獨(dú)立任務(wù):實(shí)現(xiàn)訂單數(shù)據(jù)導(dǎo)出Excel。當(dāng)時(shí)憑著學(xué)校學(xué)的基礎(chǔ)知識(shí),寫(xiě)出了這樣的代碼:

// 新手版導(dǎo)出代碼 - 內(nèi)存炸彈!
public void exportOrders(HttpServletResponse response) {
    // 1. 一次性加載全量數(shù)據(jù)
    List<Order> allOrders = orderDao.findAll(); 
    
    // 2. 創(chuàng)建Excel對(duì)象(當(dāng)時(shí)還不知道內(nèi)存代價(jià))
    Workbook workbook = new HSSFWorkbook();
    Sheet sheet = workbook.createSheet("Orders");
    
    // 3. 逐行填充數(shù)據(jù)
    int rowNum = 0;
    for (Order order : allOrders) {  // 百萬(wàn)次循環(huán)
        Row row = sheet.createRow(rowNum++);
        row.createCell(0).setCellValue(order.getId());
        row.createCell(1).setCellValue(order.getAmount());
        // ...15+個(gè)字段
    }
    
    // 4. 寫(xiě)入響應(yīng)流
    workbook.write(response.getOutputStream());
}

第一次壓測(cè)時(shí)的災(zāi)難現(xiàn)場(chǎng)

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對(duì)象創(chuàng)建

二、錯(cuò)誤分析:為什么新手代碼會(huì)OOM

三重內(nèi)存炸彈

  • 數(shù)據(jù)對(duì)象駐留內(nèi)存:百萬(wàn)條Order對(duì)象(約1.2GB)
  • Excel DOM樹(shù)爆炸:POI的HSSFWorkbook每個(gè)單元格都是獨(dú)立對(duì)象
  • 字符串拼接黑洞:字段值拼接消耗額外內(nèi)存

內(nèi)存消耗估算

組件1萬(wàn)條10萬(wàn)條100萬(wàn)條
訂單對(duì)象120MB1.2GB12GB
Excel對(duì)象(估算)200MB2GB20GB+
總內(nèi)存320MB3.2GB32GB+

當(dāng)時(shí)我用的測(cè)試機(jī)只有2G內(nèi)存...

三、解決之道:流式處理方案

架構(gòu)演進(jìn)對(duì)比

graph LR
    A[新手方案] -->|全內(nèi)存| B[OOM崩潰]
    C[優(yōu)化方案] -->|磁盤(pán)緩沖| D[成功導(dǎo)出]
    D -->|內(nèi)存控制| E[穩(wěn)定運(yùn)行]

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

public void streamExport(HttpServletResponse response) throws Exception {
    // 1. 創(chuàng)建流式工作簿(內(nèi)存中只保留100行)
    Workbook workbook = new SXSSFWorkbook(100); 
    Sheet sheet = workbook.createSheet("訂單數(shù)據(jù)");
    
    // 2. 寫(xiě)表頭
    Row header = sheet.createRow(0);
    header.createCell(0).setCellValue("ID");
    // ...其他表頭
    
    // 3. 分頁(yè)查詢(xún)+流式寫(xiě)入
    int pageSize = 2000;
    int pageNum = 1;
    int rowIndex = 1; // 數(shù)據(jù)行起始位置
    
    while (true) {
        // 4. 分頁(yè)查詢(xún)(避免全量加載)
        List<Order> page = orderDao.findByPage(pageNum, pageSize);
        if (page.isEmpty()) break;
        
        // 5. 批量寫(xiě)入當(dāng)前頁(yè)
        for (Order order : page) {
            Row row = sheet.createRow(rowIndex++);
            row.createCell(0).setCellValue(order.getId());
            // ...其他字段
        }
        
        // 6. 刷新當(dāng)前頁(yè)數(shù)據(jù)到磁盤(pán)
        ((SXSSFSheet)sheet).flushRows(page.size());
        
        pageNum++;
    }
    
    // 7. 設(shè)置響應(yīng)頭
    response.setContentType("application/vnd.ms-excel");
    response.setHeader("Content-Disposition", "attachment;filename=orders.xlsx");
    
    // 8. 流式輸出到客戶(hù)端
    workbook.write(response.getOutputStream());
    
    // 9. 清理臨時(shí)文件
    ((SXSSFWorkbook)workbook).dispose();
}

四、關(guān)鍵技術(shù)解析

1.SXSSFWorkbook 核心機(jī)制

滑動(dòng)窗口:內(nèi)存中只保留指定行數(shù)(默認(rèn)100行)

自動(dòng)刷盤(pán):超過(guò)窗口大小的行寫(xiě)入磁盤(pán)臨時(shí)文件

內(nèi)存對(duì)比:傳統(tǒng)方式 vs SXSSF

// 傳統(tǒng)方式(危險(xiǎn)?。?
Workbook workbook = new XSSFWorkbook(); 

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

2.分頁(yè)查詢(xún)優(yōu)化技巧

避免深度分頁(yè):不要使用limit 1000000,100

推薦方案:基于ID范圍的連續(xù)分頁(yè)

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

3.內(nèi)存監(jiān)控技巧

添加JVM參數(shù)觀察內(nèi)存變化:

-XX:+PrintGCDetails -Xloggc:gc.log

五、性能優(yōu)化實(shí)戰(zhàn)

樣式處理陷阱

// 錯(cuò)誤做法:每行創(chuàng)建樣式(內(nèi)存爆炸)
for(Order order : orders) {
    CellStyle style = workbook.createCellStyle();
    row.setCellStyle(style);
}

// 正確做法:樣式池復(fù)用
CellStyle moneyStyle = workbook.createCellStyle();
moneyStyle.setDataFormat(BuiltinFormats.getBuiltinFormat(4));

// 在需要時(shí)直接使用
cell.setCellStyle(moneyStyle);

臨時(shí)文件管理

// 自定義臨時(shí)文件位置(避免/tmp爆滿)
File tmpDir = new File("/data/tmp");
SXSSFWorkbook workbook = new SXSSFWorkbook(null, 100, true, tmpDir);

寫(xiě)入加速技巧

// 批量設(shè)置單元格值(減少方法調(diào)用)
Row row = sheet.createRow(0);
Object[] values = {"ID", "金額", "日期"};
for (int i = 0; i < values.length; i++) {
    row.createCell(i).setCellValue(values[i].toString());
}

六、方案效果對(duì)比

指標(biāo)新手方案流式方案
內(nèi)存占用>3GB (OOM)≈150MB
響應(yīng)時(shí)間無(wú)法完成5分鐘/百萬(wàn)行
CPU占用頻繁Full GC平穩(wěn)
代碼復(fù)雜度簡(jiǎn)單中等
可支持?jǐn)?shù)據(jù)量<1萬(wàn)行>1000萬(wàn)行

七、避坑指南:血淚經(jīng)驗(yàn)

分頁(yè)查詢(xún)的坑

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

資源關(guān)閉的坑

// 忘記關(guān)閉資源導(dǎo)致內(nèi)存泄漏
Workbook workbook = new SXSSFWorkbook();
// 必須添加finally塊關(guān)閉

數(shù)據(jù)類(lèi)型的坑

// 日期類(lèi)型特殊處理
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(workbook.createDataFormat().getFormat("yyyy-MM-dd"));
cell.setCellStyle(dateStyle);

八、老鳥(niǎo)的思考

8年Java開(kāi)發(fā)教會(huì)我處理海量數(shù)據(jù)的核心原則:

內(nèi)存有限性原則

graph LR
內(nèi)存-->磁盤(pán)-->分布式

當(dāng)內(nèi)存不夠時(shí),合理利用磁盤(pán)空間

流式處理三要素

  • 分頁(yè)加載(Paging)
  • 批量處理(Batching)
  • 即時(shí)釋放(Releasing)

資源管理箴言

"打開(kāi)的資源要及時(shí)關(guān)閉,創(chuàng)建的對(duì)象要明確生命周期"

最后建議:超過(guò)500萬(wàn)行數(shù)據(jù)建議使用CSV格式或?qū)I(yè)ETL工具,Excel畢竟不是數(shù)據(jù)庫(kù)!

以上就是Java實(shí)現(xiàn)百萬(wàn)數(shù)據(jù)導(dǎo)出Excel的詳細(xì)指南的詳細(xì)內(nèi)容,更多關(guān)于Java百萬(wàn)數(shù)據(jù)導(dǎo)出Excel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • hadoop?全面解讀自定義分區(qū)

    hadoop?全面解讀自定義分區(qū)

    Hadoop是一個(gè)由Apache基金會(huì)所開(kāi)發(fā)的分布式系統(tǒng)基礎(chǔ)架構(gòu)。用戶(hù)可以在不了解分布式底層細(xì)節(jié)的情況下,開(kāi)發(fā)分布式程序。充分利用集群的威力進(jìn)行高速運(yùn)算和存儲(chǔ)
    2022-02-02
  • 詳解SpringBoot中異步請(qǐng)求和異步調(diào)用(看完這一篇就夠了)

    詳解SpringBoot中異步請(qǐng)求和異步調(diào)用(看完這一篇就夠了)

    這篇文章主要介紹了SpringBoot中異步請(qǐng)求和異步調(diào)用問(wèn)題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-04-04
  • 關(guān)于java String中intern的深入講解

    關(guān)于java String中intern的深入講解

    這篇文章主要給大家介紹了關(guān)于java String中intern的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Java實(shí)現(xiàn)求子數(shù)組和的最大值算法示例

    Java實(shí)現(xiàn)求子數(shù)組和的最大值算法示例

    這篇文章主要介紹了Java實(shí)現(xiàn)求子數(shù)組和的最大值算法,涉及Java數(shù)組遍歷、判斷、運(yùn)算等相關(guān)操作技巧,需要的朋友可以參考下
    2018-02-02
  • 詳談java中File類(lèi)getPath()、getAbsolutePath()、getCanonical的區(qū)別

    詳談java中File類(lèi)getPath()、getAbsolutePath()、getCanonical的區(qū)別

    下面小編就為大家?guī)?lái)一篇詳談java中File類(lèi)getPath()、getAbsolutePath()、getCanonical的區(qū)別。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • 將JavaDoc注釋生成API文檔的操作

    將JavaDoc注釋生成API文檔的操作

    這篇文章主要介紹了將JavaDoc注釋生成API文檔的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • JVM常見(jiàn)垃圾收集器學(xué)習(xí)指南

    JVM常見(jiàn)垃圾收集器學(xué)習(xí)指南

    這篇文章主要為大家介紹了JVM常見(jiàn)垃圾收集器學(xué)習(xí)指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Spring IOC裝配Bean過(guò)程解析

    Spring IOC裝配Bean過(guò)程解析

    這篇文章主要介紹了Spring IOC裝配Bean過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • springboot使用自定義注解實(shí)現(xiàn)aop切面日志

    springboot使用自定義注解實(shí)現(xiàn)aop切面日志

    這篇文章主要為大家詳細(xì)介紹了springboot使用自定義注解實(shí)現(xiàn)aop切面日志,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • java實(shí)現(xiàn)十六進(jìn)制字符unicode與中英文轉(zhuǎn)換示例

    java實(shí)現(xiàn)十六進(jìn)制字符unicode與中英文轉(zhuǎn)換示例

    當(dāng)需要對(duì)一個(gè)unicode十六進(jìn)制字符串進(jìn)行編碼時(shí),首先做的應(yīng)該是確認(rèn)字符集編碼格式,在無(wú)法快速獲知的情況下,通過(guò)一下的str4all方法可以達(dá)到這一目的
    2014-02-02

最新評(píng)論