Java實(shí)現(xiàn)Excel百萬級數(shù)據(jù)導(dǎo)入功能的示例代碼
前言
在遇到大數(shù)據(jù)量excel,50MB大小或數(shù)百萬級別的數(shù)據(jù)讀取時,使用常用的POI容易導(dǎo)致讀取時內(nèi)存溢出或者cpu飆升。
本文討論的是針對xlsx格式的excel文件上傳,采用com.monitorjbl.xlsx.StreamingReader 。
什么是StreamReader
StreamReader 是 java.io 包中的一個類,用于讀取字符流的高級類。它繼承自 Reader 類,可以以字符為單位讀取文件中的數(shù)據(jù)。
StreamReader的主要功能
- 以字符為單位讀取文件中的數(shù)據(jù)
- 提供了多種讀取方法,如read()、readLine()等
- 可以指定字符編碼,以適應(yīng)不同類型的文件
StreamReader的優(yōu)勢
簡化了文件讀取的過程,提供了高層次的讀取方法可以處理不同類型的文件,如文本文件、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 { // 開始時間 Long begin = new Date().getTime(); // excel轉(zhuǎn)換為List集合(約30s~40s) List<TpInstallationMaintenanceLabelDetailed> tpInstallationMaintenanceLabelDetailedList = largeFilesUtils.importExcelLargeFile(file, updateSupport); // 結(jié)束時間 Long end = new Date().getTime(); // 數(shù)據(jù)導(dǎo)入(約30s) String message = importInstallationMaintenanceLabelDetailed(tpInstallationMaintenanceLabelDetailedList, updateSupport); // 總用時(約60s~70s) message = message +"<br/>數(shù)據(jù)轉(zhuǎn)換花費(fèi)時間 : "+(end - begin) / 1000 + " s" ; // 返回 return AjaxResult.success(message); }
2、Excel數(shù)據(jù)轉(zhuǎn)為List
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 { // 大文件測試開始 InputStream inputStream = file.getInputStream(); // com.monitorjbl.xlsx.StreamingReader Workbook workbook = StreamingReader.builder().rowCacheSize(1000) // 緩存到內(nèi)存中的行數(shù)(默認(rèn)是10) .bufferSize(10240) // 讀取資源時,緩存到內(nèi)存的字節(jié)大小(默認(rèn)是1024) .open(inputStream); // 獲取第一個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í)行開始時間 Long begin = new Date().getTime(); // 線程數(shù) final int THREAD_COUNT = 10; // 每個線程處理的數(shù)據(jù)量 final int BATCH_SIZE = tpInstallationMaintenanceLabelDetailedList.size() / THREAD_COUNT; // ExecutorService是Java中對線程池定義的一個接口 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ù)庫連接對象 Connection conn = null; // 初始化預(yù)編譯的 SQL 語句的對象 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è)置自動提交模式,默認(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ù)編譯對象 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ù),但會把已添加的任務(wù)執(zhí)行完 executor.shutdown(); // 等待所有線程完成任務(wù) while (!executor.isTerminated()) {} System.out.println("完成"); // 結(jié)束時間 Long end = new Date().getTime(); // 耗時 logger.debug(tpInstallationMaintenanceLabelDetailedList.size()+"條數(shù)據(jù)插入花費(fèi)時間 : " + (end - begin) / 1000 + " s"); // return "數(shù)據(jù)導(dǎo)入成功!共 " + tpInstallationMaintenanceLabelDetailedList.size() + " 條!"+"<br/>數(shù)據(jù)導(dǎo)入花費(fèi)時間 : "+(end - begin) / 1000 + " s" ; }
到此這篇關(guān)于Java實(shí)現(xiàn)Excel百萬級數(shù)據(jù)導(dǎo)入功能的示例代碼的文章就介紹到這了,更多相關(guān)Java百萬級數(shù)據(jù)導(dǎo)入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot開發(fā)時Java對象和Json對象之間的轉(zhuǎn)換
在Spring?Boot開發(fā)中,我們經(jīng)常需要處理Java對象和Json對象之間的轉(zhuǎn)換,本文將介紹如何在Spring?Boot項(xiàng)目中實(shí)現(xiàn)Java對象和Json對象之間的轉(zhuǎn)換,感興趣的朋友跟隨小編一起看看吧2023-09-09java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法
這篇文章主要介紹了java中javamail發(fā)送帶附件的郵件實(shí)現(xiàn)方法,較為詳細(xì)的分析了JavaMail發(fā)送郵件的用法,是非常實(shí)用的技巧,需要的朋友可以參考下2015-01-01springboot 使用ThreadLocal的實(shí)例代碼
這篇文章主要介紹了springboot 使用ThreadLocal的實(shí)例代碼,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12springboot @Configuration和@Componment的區(qū)別及說明
這篇文章主要介紹了springboot @Configuration和@Componment的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06Java實(shí)現(xiàn)默認(rèn)目錄查看與修改的方法
本項(xiàng)目介紹了Java如何獲取和修改默認(rèn)目錄,并通過示例代碼展示了如何在默認(rèn)目錄下創(chuàng)建和操作文件,盡管Java無法直接更改全局默認(rèn)目錄,但可以通過System.setProperty間接影響部分API的默認(rèn)行為,需要的朋友可以參考下2025-03-03Java中的getClass()以及getName()方法使用
這篇文章主要介紹了Java中的getClass()以及getName()方法使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12基于Java實(shí)現(xiàn)經(jīng)典蜘蛛紙牌游戲
《蜘蛛紙牌》(Ancient?Spider)?是由Oberon?Games開發(fā)的一款休閑益智類游戲。本文將利用Java語言實(shí)現(xiàn)這一經(jīng)典游戲,需要的可以參考一下2022-05-05