Java實(shí)現(xiàn)Excel百萬級(jí)數(shù)據(jù)導(dǎo)入功能的示例代碼
前言
在遇到大數(shù)據(jù)量excel,50MB大小或數(shù)百萬級(jí)別的數(shù)據(jù)讀取時(shí),使用常用的POI容易導(dǎo)致讀取時(shí)內(nèi)存溢出或者cpu飆升。
本文討論的是針對(duì)xlsx格式的excel文件上傳,采用com.monitorjbl.xlsx.StreamingReader 。
什么是StreamReader
StreamReader 是 java.io 包中的一個(gè)類,用于讀取字符流的高級(jí)類。它繼承自 Reader 類,可以以字符為單位讀取文件中的數(shù)據(jù)。
StreamReader的主要功能
- 以字符為單位讀取文件中的數(shù)據(jù)
- 提供了多種讀取方法,如read()、readLine()等
- 可以指定字符編碼,以適應(yīng)不同類型的文件
StreamReader的優(yōu)勢(shì)
簡(jiǎn)化了文件讀取的過程,提供了高層次的讀取方法可以處理不同類型的文件,如文本文件、CSV文件等
可以讀取大型文件,節(jié)省內(nèi)存空間
注:StreamReader只能用遍歷形式讀取數(shù)據(jù)
Sheet sheet = wk.getSheetAt(0);
//遍歷所有的行
for (Row row : sheet) {
System.out.println("開始遍歷第" + row.getRowNum() + "行數(shù)據(jù):");
//遍歷所有的列
for (Cell cell : row) {
System.out.print(cell.getStringCellValue() + " ");
}
System.out.println(" ");
}
案例步驟
1、導(dǎo)入文件前端接口
Controller.java
/**
* 導(dǎo)入文件前端接口
*/
@PostMapping("/importData")
@ResponseBody
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
// 開始時(shí)間
Long begin = new Date().getTime();
// excel轉(zhuǎn)換為L(zhǎng)ist集合(約30s~40s)
List<TpInstallationMaintenanceLabelDetailed> tpInstallationMaintenanceLabelDetailedList = largeFilesUtils.importExcelLargeFile(file, updateSupport);
// 結(jié)束時(shí)間
Long end = new Date().getTime();
// 數(shù)據(jù)導(dǎo)入(約30s)
String message = importInstallationMaintenanceLabelDetailed(tpInstallationMaintenanceLabelDetailedList, updateSupport);
// 總用時(shí)(約60s~70s)
message = message +"<br/>數(shù)據(jù)轉(zhuǎn)換花費(fèi)時(shí)間 : "+(end - begin) / 1000 + " s" ;
// 返回
return AjaxResult.success(message);
}2、Excel數(shù)據(jù)轉(zhuǎn)為L(zhǎng)ist
largeFilesUtils.java
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.monitorjbl.xlsx.StreamingReader;
import com.ruoyi.huawei.domain.TpInstallationMaintenanceLabelDetailed;
/**
* 大文件Excel導(dǎo)入
*
* @author y
* @date 2024-03-29
*/
@Service
public class LargeFilesUtils {
/**
* 大文件Excel導(dǎo)入
*
* @param
* @return 工具
*/
public List<TpInstallationMaintenanceLabelDetailed> importExcelLargeFile(MultipartFile file,
boolean updateSupport) {
List<TpInstallationMaintenanceLabelDetailed> tpInstallationMaintenanceLabelDetailedList = new ArrayList<TpInstallationMaintenanceLabelDetailed>();
try {
// 大文件測(cè)試開始
InputStream inputStream = file.getInputStream();
// com.monitorjbl.xlsx.StreamingReader
Workbook workbook = StreamingReader.builder().rowCacheSize(1000) // 緩存到內(nèi)存中的行數(shù)(默認(rèn)是10)
.bufferSize(10240) // 讀取資源時(shí),緩存到內(nèi)存的字節(jié)大小(默認(rèn)是1024)
.open(inputStream);
// 獲取第一個(gè)Shhet
Sheet sheet = workbook.getSheetAt(0);
//
boolean fastRowBoolean = true;
// monitorjbl只能支持遍歷,不能通過指定下標(biāo)獲取
for (Row row : sheet) {
// 判斷是否首行
if(fastRowBoolean) {
// 設(shè)置為非首行
fastRowBoolean = false;
// continue 語句用于跳過當(dāng)前循環(huán)中剩余的代碼,并開始下一次迭代。
continue;
}
// 創(chuàng)建實(shí)體
TpInstallationMaintenanceLabelDetailed rowData = new TpInstallationMaintenanceLabelDetailed();
// 列下標(biāo)初始化
int n = 0;
// 遍歷列
for (Cell cell : row) {
//
switch (n) {
// 第一列
case 0:
rowData.setPppoeAccount(cell.getStringCellValue());
break;
// 第二列
case 1:
rowData.setInstallationMaintenanceName(cell.getStringCellValue());
break;
case 2:
rowData.setCounty(cell.getStringCellValue());
break;
case 3:
rowData.setPoorQualityUser(cell.getStringCellValue());
break;
case 4:
rowData.setOldLightCat(cell.getStringCellValue());
break;
case 5:
rowData.setSetTopBoxWirelessConnection(cell.getStringCellValue());
break;
case 6:
rowData.setPleaseUseXgponOnu(cell.getStringCellValue());
break;
case 7:
rowData.setHighTemperatureLightCat(cell.getStringCellValue());
break;
case 8:
rowData.setAnOldSetTopBox(cell.getStringCellValue());
break;
case 9:
rowData.setTwoOldSetTopBoxes(cell.getStringCellValue());
break;
case 10:
rowData.setThreeOldSetTopBoxes(cell.getStringCellValue());
break;
case 11:
rowData.setAnPoorQualityRouter(cell.getStringCellValue());
break;
case 12:
rowData.setTwoPoorQualityRouters(cell.getStringCellValue());
break;
case 13:
rowData.setThreePoorQualityRouters(cell.getStringCellValue());
break;
case 14:
rowData.setThreeOrMoreLowQualityRouters(cell.getStringCellValue());
break;
case 15:
rowData.setThreeOrMoreOldSetTopBoxes(cell.getStringCellValue());
break;
case 16:
rowData.setSeverelyPoorQualityUsersAndOldOpticalCats(cell.getStringCellValue());
break;
// 處理其他屬性
default:
break;
}
// 列下標(biāo)+1
n = n+1;
}
tpInstallationMaintenanceLabelDetailedList.add(rowData);
}
workbook.close();
} catch (Exception e) {
// TODO: handle exception
System.out.println(e);
}
return tpInstallationMaintenanceLabelDetailedList;
}
}3、List集合數(shù)據(jù)導(dǎo)入
importInstallationMaintenanceLabelDetailed
/**
* 導(dǎo)入文件分析
*/
public String importInstallationMaintenanceLabelDetailed(List<TpInstallationMaintenanceLabelDetailed> tpInstallationMaintenanceLabelDetailedList, Boolean isUpdateSupport)
{
if (StringUtils.isNull(tpInstallationMaintenanceLabelDetailedList) || tpInstallationMaintenanceLabelDetailedList.size() == 0)
{
throw new ServiceException("導(dǎo)入數(shù)據(jù)不能為空!");
}
// 執(zhí)行開始時(shí)間
Long begin = new Date().getTime();
// 線程數(shù)
final int THREAD_COUNT = 10;
// 每個(gè)線程處理的數(shù)據(jù)量
final int BATCH_SIZE = tpInstallationMaintenanceLabelDetailedList.size() / THREAD_COUNT;
// ExecutorService是Java中對(duì)線程池定義的一個(gè)接口
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
//
for (int i = 0; i < THREAD_COUNT; i++) {
// List數(shù)據(jù)開始下標(biāo)
final int startIndex = i * BATCH_SIZE;
// List數(shù)據(jù)結(jié)束下標(biāo)
final int endIndex = (i + 1) * BATCH_SIZE;
// 線程池執(zhí)行
executor.submit(new Runnable() {
public void run() {
// 初始化數(shù)據(jù)庫連接對(duì)象
Connection conn = null;
// 初始化預(yù)編譯的 SQL 語句的對(duì)象
PreparedStatement ps = null;
try {
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/tool_platform_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&useSSL=false", "root", "123456");//獲取連接
// 設(shè)置自動(dòng)提交模式,默認(rèn)true
conn.setAutoCommit(false);
// sql前綴
String prefix = "INSERT INTO tp_label_detailed ("
+ "account,"
+ "maintenance_name,"
+ "county,quality_user,"
+ "light_cat,wireless_connection,"
+ "xgpon_onu,"
+ "light_cat,"
+ "an_box,two_boxes,"
+ "three_boxes,"
+ "an_router,"
+ "two_routers,"
+ "three_routers,"
+ "three_or_more_routers,"
+ "three_or_more_boxes,"
+ "severely_and_cats"
+ ") VALUES ";
// 創(chuàng)建預(yù)編譯對(duì)象
ps = conn.prepareStatement(prefix);
// 保存sql后綴
StringBuffer suffix = new StringBuffer();
// 執(zhí)行條數(shù)
int number_of_cycles = 0;
//
for (int j = startIndex; j < endIndex; j++) {
// 拼接sql
suffix.append("("+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getPppoeAccount()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getInstallationMaintenanceName()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getCounty()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getPoorQualityUser()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getOldLightCat()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getSetTopBoxWirelessConnection()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getPleaseUseXgponOnu()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getHighTemperatureLightCat()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getAnOldSetTopBox()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getTwoOldSetTopBoxes()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getThreeOldSetTopBoxes()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getAnPoorQualityRouter()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getTwoPoorQualityRouters()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getThreePoorQualityRouters()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getThreeOrMoreLowQualityRouters()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getThreeOrMoreOldSetTopBoxes()+"',"+
"'"+tpInstallationMaintenanceLabelDetailedList.get(j).getSeverelyPoorQualityUsersAndOldOpticalCats()+"'"+
"),"); //拼接sql
number_of_cycles = number_of_cycles +1;
}
// sql拼接
String sql = prefix + suffix.substring(0, suffix.length() - 1);
// 添加預(yù)處理sql
ps.addBatch(sql);
// 執(zhí)行語句
ps.executeBatch();
// 提交
conn.commit();
// 初始化拼接sql
suffix.setLength(0);
// 初始化條數(shù)
number_of_cycles = 1;
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (ps != null) {
try {
// 關(guān)閉ps
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
// 關(guān)閉數(shù)據(jù)庫連接
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
});
}
//關(guān)閉線程池,不接受新任務(wù),但會(huì)把已添加的任務(wù)執(zhí)行完
executor.shutdown();
// 等待所有線程完成任務(wù)
while (!executor.isTerminated()) {}
System.out.println("完成");
// 結(jié)束時(shí)間
Long end = new Date().getTime();
// 耗時(shí)
logger.debug(tpInstallationMaintenanceLabelDetailedList.size()+"條數(shù)據(jù)插入花費(fèi)時(shí)間 : " + (end - begin) / 1000 + " s");
//
return "數(shù)據(jù)導(dǎo)入成功!共 " + tpInstallationMaintenanceLabelDetailedList.size() + " 條!"+"<br/>數(shù)據(jù)導(dǎo)入花費(fèi)時(shí)間 : "+(end - begin) / 1000 + " s" ;
}到此這篇關(guān)于Java實(shí)現(xiàn)Excel百萬級(jí)數(shù)據(jù)導(dǎo)入功能的示例代碼的文章就介紹到這了,更多相關(guān)Java百萬級(jí)數(shù)據(jù)導(dǎo)入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解在Java程序中運(yùn)用Redis緩存對(duì)象的方法
這篇文章主要介紹了在Java程序中運(yùn)用Redis緩存對(duì)象的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Spring?Boot開發(fā)時(shí)Java對(duì)象和Json對(duì)象之間的轉(zhuǎn)換
在Spring?Boot開發(fā)中,我們經(jīng)常需要處理Java對(duì)象和Json對(duì)象之間的轉(zhuǎn)換,本文將介紹如何在Spring?Boot項(xiàng)目中實(shí)現(xiàn)Java對(duì)象和Json對(duì)象之間的轉(zhuǎn)換,感興趣的朋友跟隨小編一起看看吧2023-09-09
java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法
這篇文章主要介紹了java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法,較為詳細(xì)的分析了JavaMail發(fā)送郵件的用法,是非常實(shí)用的技巧,需要的朋友可以參考下2015-01-01
springboot 使用ThreadLocal的實(shí)例代碼
這篇文章主要介紹了springboot 使用ThreadLocal的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
springboot @Configuration和@Componment的區(qū)別及說明
這篇文章主要介紹了springboot @Configuration和@Componment的區(qū)別及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
Java實(shí)現(xiàn)默認(rèn)目錄查看與修改的方法
本項(xiàng)目介紹了Java如何獲取和修改默認(rèn)目錄,并通過示例代碼展示了如何在默認(rèn)目錄下創(chuàng)建和操作文件,盡管Java無法直接更改全局默認(rèn)目錄,但可以通過System.setProperty間接影響部分API的默認(rèn)行為,需要的朋友可以參考下2025-03-03
Java中的getClass()以及getName()方法使用
這篇文章主要介紹了Java中的getClass()以及getName()方法使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
基于Java實(shí)現(xiàn)經(jīng)典蜘蛛紙牌游戲
《蜘蛛紙牌》(Ancient?Spider)?是由Oberon?Games開發(fā)的一款休閑益智類游戲。本文將利用Java語言實(shí)現(xiàn)這一經(jīng)典游戲,需要的可以參考一下2022-05-05

