Java EasyExcel 匯出報記憶體溢位如何解決

威哥爱编程發表於2024-10-28

大家好,我是 V 哥。使用EasyExcel進行大資料量匯出時容易導致記憶體溢位,特別是在匯出百萬級別的資料時。你有遇到過這種情況嗎,以下是V 哥整理的解決該問題的一些常見方法,分享給大家,歡迎一起討論:

EasyExcel大資料量匯出常見方法

1. 分批寫入

  • EasyExcel支援分批寫入資料,可以將資料分批載入到記憶體中,分批寫入Excel檔案,避免一次性將大量資料載入到記憶體中。
  • 示例程式碼
     String fileName = "large_data.xlsx";
     ExcelWriter excelWriter = EasyExcel.write(fileName).build();
     WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build();

     // 假設每次寫入10000條資料
     int batchSize = 10000;
     List<Data> dataList;
     int pageIndex = 0;
     do {
         // 分頁獲取資料
         dataList = getDataByPage(pageIndex++, batchSize);
         excelWriter.write(dataList, writeSheet);
     } while (dataList.size() == batchSize);

     // 關閉資源
     excelWriter.finish();

2. 設定合適的JVM記憶體

  • 針對大資料匯出場景,可以嘗試增大JVM的記憶體分配,例如:
     java -Xms512M -Xmx4G -jar yourApp.jar
  • 解釋
    • -Xms512M:設定初始堆大小為512MB。
    • -Xmx4G:設定最大堆大小為4GB。

3. 減少資料物件的複雜性

  • 匯出資料時,儘量簡化資料物件,避免不必要的巢狀和多餘欄位的載入,以減少物件佔用的記憶體空間。

4. 關閉自動列寬設定

  • EasyExcel的自動列寬功能會佔用大量記憶體,特別是在資料量較大的情況下。關閉自動列寬可以節省記憶體。
  • 示例程式碼
     EasyExcel.write(fileName)
             .registerWriteHandler(new SimpleWriteHandler()) // 不使用自動列寬
             .sheet("Sheet1")
             .doWrite(dataList);

5. 使用Stream匯出(適合大資料)

  • 利用OutputStream分批寫入資料,減少記憶體消耗。透過BufferedOutputStream可以進一步提高效能。
  • 示例程式碼
     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. 選擇合適的資料匯出工具

  • 如果資料量非常大,可以考慮切換到支援更高效能的匯出工具(如Apache POI的SXSSFWorkbook),適合匯出百萬級別資料量,但配置和使用會更復雜。

亮點來了,那要如何使用 POI 的 SXSSFWorkbook來匯出百萬級別的資料量呢?

Apache POI的SXSSFWorkbook 實現百萬級別資料量的匯出案例

使用Apache POI的SXSSFWorkbook可以處理大資料量的Excel匯出,因為SXSSFWorkbook基於流式寫入,不會將所有資料載入到記憶體中,而是使用臨時檔案進行快取,這樣可以顯著減少記憶體消耗,適合百萬級別資料的匯出。下面我們來看一個完整的實現示例。

程式碼如下

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";
        
        // 匯出百萬級資料
        exportLargeData(filePath);
    }

    private static void exportLargeData(String filePath) {
        // 每次寫入的批次大小
        final int batchSize = 10000;
        // 資料總條數
        final int totalRows = 1_000_000;

        // 建立SXSSFWorkbook物件,記憶體中只保留100行,超過的部分會寫入臨時檔案
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        workbook.setCompressTempFiles(true); // 啟用臨時檔案壓縮

        // 建立工作表
        Sheet sheet = workbook.createSheet("Large Data");

        // 建立標題行
        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; // 資料開始的行號

        try {
            // 按批次寫入資料
            for (int i = 0; i < totalRows / batchSize; i++) {
                // 模擬獲取每批資料
                List<Data> dataList = getDataBatch(rowNum, batchSize);
                
                // 將資料寫入到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());
                }

                // 處理完成一批資料後,可以選擇清除快取資料,防止記憶體溢位
                ((SXSSFSheet) sheet).flushRows(batchSize); // 清除已寫的行快取
            }

            // 將資料寫入檔案
            try (FileOutputStream fos = new FileOutputStream(filePath)) {
                workbook.write(fos);
            }
            System.out.println("資料匯出完成:" + filePath);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉workbook並刪除臨時檔案
            workbook.dispose();
        }
    }

    /**
     * 模擬分頁獲取資料
     */
    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;
    }

    // 資料類
    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;
        }
    }
}

來解釋一下程式碼

  1. SXSSFWorkbookSXSSFWorkbook(100)表示記憶體中最多保留100行資料,超過的部分會寫入臨時檔案,節省記憶體。
  2. 批次處理:透過batchSize控制每批次寫入的資料量,以減少記憶體消耗。totalRows設定為1,000,000表示匯出100萬條資料。
  3. 模擬資料生成getDataBatch方法模擬分頁獲取資料,每次返回一批資料。
  4. 清除快取行:每次寫入一批資料後,透過flushRows(batchSize)將快取的行從記憶體中清除,以控制記憶體佔用。
  5. 壓縮臨時檔案workbook.setCompressTempFiles(true)啟用臨時檔案壓縮,進一步減少磁碟空間佔用。

需要注意的事項

  • 臨時檔案:SXSSFWorkbook會在系統臨時資料夾中生成臨時檔案,需要確保磁碟空間足夠。
  • 資源釋放:完成資料寫入後需要呼叫workbook.dispose()以清理臨時檔案。
  • 效能最佳化:可根據機器記憶體調整batchSizeSXSSFWorkbook快取行數,避免頻繁重新整理和記憶體溢位。

相關文章