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)方法,分享給大家,歡迎一起討論:
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。 EasyExcel.write(fileName)
.registerWriteHandler(new SimpleWriteHandler()) // 不使用自動(dòng)列寬
.sheet("Sheet1")
.doWrite(dataList);
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();
}
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ù)據(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;
}
}
}
SXSSFWorkbook(100)
表示內(nèi)存中最多保留100行數(shù)據(jù),超過(guò)的部分會(huì)寫(xiě)入臨時(shí)文件,節(jié)省內(nèi)存。batchSize
控制每批次寫(xiě)入的數(shù)據(jù)量,以減少內(nèi)存消耗。totalRows
設(shè)置為1,000,000表示導(dǎo)出100萬(wàn)條數(shù)據(jù)。getDataBatch
方法模擬分頁(yè)獲取數(shù)據(jù),每次返回一批數(shù)據(jù)。flushRows(batchSize)
將緩存的行從內(nèi)存中清除,以控制內(nèi)存占用。workbook.setCompressTempFiles(true)
啟用臨時(shí)文件壓縮,進(jìn)一步減少磁盤(pán)空間占用。需要注意的事項(xiàng)
workbook.dispose()
以清理臨時(shí)文件。batchSize
和SXSSFWorkbook
緩存行數(shù),避免頻繁刷新和內(nèi)存溢出。EasyExcel大數(shù)據(jù)量導(dǎo)出優(yōu)化及Apache POI的SXSSFWorkbook實(shí)踐
更多建議: