EasyExcel大數(shù)據(jù)量導(dǎo)出優(yōu)化及Apache POI的SXSSFWorkbook實(shí)踐

2024-12-03 11:38 更新

Welcome to the Java原創(chuàng)學(xué)習(xí)手冊(cè) wiki!大家好,我是 V 哥。使用EasyExcel進(jìn)行大數(shù)據(jù)量導(dǎo)出時(shí)容易導(dǎo)致內(nèi)存溢出,特別是在導(dǎo)出百萬(wàn)級(jí)別的數(shù)據(jù)時(shí)。你有遇到過(guò)這種情況嗎,以下是V 哥整理的解決該問(wèn)題的一些常見(jiàn)方法,分享給大家,歡迎一起討論:

EasyExcel大數(shù)據(jù)量導(dǎo)出常見(jiàn)方法

1. 分批寫(xiě)入

  • EasyExcel支持分批寫(xiě)入數(shù)據(jù),可以將數(shù)據(jù)分批加載到內(nèi)存中,分批寫(xiě)入Excel文件,避免一次性將大量數(shù)據(jù)加載到內(nèi)存中。
  • 示例代碼
    
     String fileName = "large_data.xlsx";
     ExcelWriter excelWriter = EasyExcel.write(fileName).build();
     WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build();

// 假設(shè)每次寫(xiě)入10000條數(shù)據(jù) int batchSize = 10000; List<Data> dataList; int pageIndex = 0; do { // 分頁(yè)獲取數(shù)據(jù) dataList = getDataByPage(pageIndex++, batchSize); excelWriter.write(dataList, writeSheet); } while (dataList.size() == batchSize);

// 關(guān)閉資源 excelWriter.finish();



#### 2. 設(shè)置合適的JVM內(nèi)存
- 針對(duì)大數(shù)據(jù)導(dǎo)出場(chǎng)景,可以嘗試增大JVM的內(nèi)存分配,例如:
```shell
     java -Xms512M -Xmx4G -jar yourApp.jar
  • 解釋
    • -Xms512M:設(shè)置初始堆大小為512MB。
    • -Xmx4G:設(shè)置最大堆大小為4GB。

3. 減少數(shù)據(jù)對(duì)象的復(fù)雜性

  • 導(dǎo)出數(shù)據(jù)時(shí),盡量簡(jiǎn)化數(shù)據(jù)對(duì)象,避免不必要的嵌套和多余字段的加載,以減少對(duì)象占用的內(nèi)存空間。

4. 關(guān)閉自動(dòng)列寬設(shè)置

  • EasyExcel的自動(dòng)列寬功能會(huì)占用大量?jī)?nèi)存,特別是在數(shù)據(jù)量較大的情況下。關(guān)閉自動(dòng)列寬可以節(jié)省內(nèi)存。
  • 示例代碼
     EasyExcel.write(fileName)
             .registerWriteHandler(new SimpleWriteHandler()) // 不使用自動(dòng)列寬
             .sheet("Sheet1")
             .doWrite(dataList);

5. 使用Stream導(dǎo)出(適合大數(shù)據(jù))

  • 利用OutputStream分批寫(xiě)入數(shù)據(jù),減少內(nèi)存消耗。通過(guò)BufferedOutputStream可以進(jìn)一步提高性能。
  • 示例代碼
     try (OutputStream out = new BufferedOutputStream(new FileOutputStream(fileName))) {
         ExcelWriter excelWriter = EasyExcel.write(out).build();
         WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build();
         int pageIndex = 0;
         List<Data> dataList;
         do {
             dataList = getDataByPage(pageIndex++, batchSize);
             excelWriter.write(dataList, writeSheet);
         } while (dataList.size() == batchSize);
         excelWriter.finish();
     } catch (IOException e) {
         e.printStackTrace();
     }

6. 選擇合適的數(shù)據(jù)導(dǎo)出工具

  • 如果數(shù)據(jù)量非常大,可以考慮切換到支持更高性能的導(dǎo)出工具(如Apache POI的SXSSFWorkbook),適合導(dǎo)出百萬(wàn)級(jí)別數(shù)據(jù)量,但配置和使用會(huì)更復(fù)雜。

亮點(diǎn)來(lái)了,那要如何使用 POI 的 SXSSFWorkbook來(lái)導(dǎo)出百萬(wàn)級(jí)別的數(shù)據(jù)量呢?

Apache POI的SXSSFWorkbook 實(shí)現(xiàn)百萬(wàn)級(jí)別數(shù)據(jù)量的導(dǎo)出案例

使用Apache POI的SXSSFWorkbook可以處理大數(shù)據(jù)量的Excel導(dǎo)出,因?yàn)?code>SXSSFWorkbook基于流式寫(xiě)入,不會(huì)將所有數(shù)據(jù)加載到內(nèi)存中,而是使用臨時(shí)文件進(jìn)行緩存,這樣可以顯著減少內(nèi)存消耗,適合百萬(wàn)級(jí)別數(shù)據(jù)的導(dǎo)出。下面我們來(lái)看一個(gè)完整的實(shí)現(xiàn)示例。

代碼如下

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;


import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


public class LargeDataExportExample {


    public static void main(String[] args) {
        // 文件輸出路徑
        String filePath = "vg_large_data_export.xlsx";

        
        // 導(dǎo)出百萬(wàn)級(jí)數(shù)據(jù)
        exportLargeData(filePath);
    }


    private static void exportLargeData(String filePath) {
        // 每次寫(xiě)入的批次大小
        final int batchSize = 10000;
        // 數(shù)據(jù)總條數(shù)
        final int totalRows = 1_000_000;


        // 創(chuàng)建SXSSFWorkbook對(duì)象,內(nèi)存中只保留100行,超過(guò)的部分會(huì)寫(xiě)入臨時(shí)文件
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        workbook.setCompressTempFiles(true); // 啟用臨時(shí)文件壓縮


        // 創(chuàng)建工作表
        Sheet sheet = workbook.createSheet("Large Data");


        // 創(chuàng)建標(biāo)題行
        Row headerRow = sheet.createRow(0);
        String[] headers = {"ID", "Name", "Age"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
        }


        int rowNum = 1; // 數(shù)據(jù)開(kāi)始的行號(hào)


        try {
            // 按批次寫(xiě)入數(shù)據(jù)
            for (int i = 0; i < totalRows / batchSize; i++) {
                // 模擬獲取每批數(shù)據(jù)
                List<Data> dataList = getDataBatch(rowNum, batchSize);

                
                // 將數(shù)據(jù)寫(xiě)入到Excel中
                for (Data data : dataList) {
                    Row row = sheet.createRow(rowNum++);
                    row.createCell(0).setCellValue(data.getId());
                    row.createCell(1).setCellValue(data.getName());
                    row.createCell(2).setCellValue(data.getAge());
                }


                // 處理完成一批數(shù)據(jù)后,可以選擇清除緩存數(shù)據(jù),防止內(nèi)存溢出
                ((SXSSFSheet) sheet).flushRows(batchSize); // 清除已寫(xiě)的行緩存
            }


            // 將數(shù)據(jù)寫(xiě)入文件
            try (FileOutputStream fos = new FileOutputStream(filePath)) {
                workbook.write(fos);
            }
            System.out.println("數(shù)據(jù)導(dǎo)出完成:" + filePath);


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關(guān)閉workbook并刪除臨時(shí)文件
            workbook.dispose();
        }
    }


    /**
     * 模擬分頁(yè)獲取數(shù)據(jù)
     */
    private static List<Data> getDataBatch(int startId, int batchSize) {
        List<Data> dataList = new ArrayList<>(batchSize);
        for (int i = 0; i < batchSize; i++) {
            dataList.add(new Data(startId + i, "Name" + (startId + i), 20 + (startId + i) % 50));
        }
        return dataList;
    }


    // 數(shù)據(jù)類(lèi)
    static class Data {
        private final int id;
        private final String name;
        private final int age;


        public Data(int id, String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }


        public int getId() {
            return id;
        }


        public String getName() {
            return name;
        }


        public int getAge() {
            return age;
        }
    }
}

來(lái)解釋一下代碼

  1. SXSSFWorkbookSXSSFWorkbook(100)表示內(nèi)存中最多保留100行數(shù)據(jù),超過(guò)的部分會(huì)寫(xiě)入臨時(shí)文件,節(jié)省內(nèi)存。
  2. 批次處理:通過(guò)batchSize控制每批次寫(xiě)入的數(shù)據(jù)量,以減少內(nèi)存消耗。totalRows設(shè)置為1,000,000表示導(dǎo)出100萬(wàn)條數(shù)據(jù)。
  3. 模擬數(shù)據(jù)生成getDataBatch方法模擬分頁(yè)獲取數(shù)據(jù),每次返回一批數(shù)據(jù)。
  4. 清除緩存行:每次寫(xiě)入一批數(shù)據(jù)后,通過(guò)flushRows(batchSize)將緩存的行從內(nèi)存中清除,以控制內(nèi)存占用。
  5. 壓縮臨時(shí)文件workbook.setCompressTempFiles(true)啟用臨時(shí)文件壓縮,進(jìn)一步減少磁盤(pán)空間占用。

需要注意的事項(xiàng)

  • 臨時(shí)文件:SXSSFWorkbook會(huì)在系統(tǒng)臨時(shí)文件夾中生成臨時(shí)文件,需要確保磁盤(pán)空間足夠。
  • 資源釋放:完成數(shù)據(jù)寫(xiě)入后需要調(diào)用workbook.dispose()以清理臨時(shí)文件。
  • 性能優(yōu)化:可根據(jù)機(jī)器內(nèi)存調(diào)整batchSizeSXSSFWorkbook緩存行數(shù),避免頻繁刷新和內(nèi)存溢出。EasyExcel大數(shù)據(jù)量導(dǎo)出優(yōu)化及Apache POI的SXSSFWorkbook實(shí)踐
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)