日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747


 

最近在處理下載支付寶賬單的需求,支付寶都有代碼示例,功能完成還是比較簡單的,唯一的問題就在于下載后的文件數據讀取。賬單文件可大可小,要保證其可用以及性能就不能簡單粗暴地完成開發就行。

文件下載是是csv格式,此文件按照行讀取,每一行中各列數據直接用逗號,隔開的。

前置設置:

 

  1. 開啟了設置內存大小以及GC日志輸出配置-Xms800m -Xmx800m -XX:+PrintGCDetails
  2. 測試文件total-File.csv數據量: 100萬,文件大小:176M
  3. 定義賬單文件的屬性字段:
private static final List ALI_FINANCE_LIST = new ArrayList<>( Arrays.asList("FINANCE_FLOW_NUMBER", "BUSINESS_FLOW_NUMBER", "MERCHANT_ORDER_NUMBER", "ITEM_NAME", "CREATION_TIME", "OPPOSITE_ACCOUNT", "RECEIPT_AMOUNT", "PAYMENT_AMOUNT", "ACCOUNT_BALANCE", "BUSINESS_CHANNEL", "BUSINESS_TYPE", "REMARK")); 復制代碼

 

相關推薦閱讀:

圖形化監控工具Jconsole

虛擬機的日志和日志參數

第一版:簡單粗暴

直來直往,毫無技巧

 

拿到文件流,直接按行讀取,把所有的數據放入到List中(其中業務相關的校驗以及數據篩選都去掉了)

代碼如下

@ApiOperation(value = "測試解析-簡單粗暴版") @GetMApping("/readFileV1") public ResponseEntity readFileV1(){ File file = new File("/Users/ajisun/projects/alwaysCoding/files/total-file.csv"); List> context = new ArrayList<>(); try ( InputStream stream = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(stream, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr) ) { String line = ""; int number = 1; while ((line = br.readLine()) != null) { //去除#號開始的行 if (!line.startsWith("#")) { if (number >= 1) { //csv是以逗號為區分的文件,以逗號區分 String[] columns = line.split(",", -1); //構建數據 Map dataMap = new HashMap<>(16); for (int i = 0; i < columns.length; i++) { //防止異常,大于預定義的列不處理 if (i > ALI_FINANCE_LIST.size()) { break; } dataMap.put(ALI_FINANCE_LIST.get(i), columns[i].trim()); } context.add(dataMap); } number++; } } // TODO 存表 System.out.println("=====插入數據庫,數據條數:"+context.size()); System.out.println("對象大小:"+(ObjectSizeCalculator.getObjectSize(context)/1048576) +" M"); context.clear(); } catch (Exception e) { e.printStackTrace(); } return null; } 復制代碼

輸出日志以及Jconsole的監控如下


 


 

 

由上面的圖可以看出內存和CPU的使用率都比較高,會不斷觸發Full GC,最終還出現了OOM,內存基本使用完了,cpu使用也達到了近70%。

 

去除-Xms800m -Xmx800m的內存大小限制后可以把全部數據拿到,結果如下圖所示


 


 

 

所有數據可以正常解析讀取,Full GC也沒用前一次頻繁,沒有出現OOM。10w條數據大小有1.2G,所占用的內存更是達到2.5G,CPU也是近60%的使用率。

 

僅僅是200M的csv文件,堆內存就占用了2.5G,如果是更大的文件,內存占用不得起飛了

嚴重占用了系統資源,對于大文件,此方法不可取。

第二版:循序漸進

緩緩圖之,數據分批

 

第一版內存、CPU占用過大,甚至OOM,主要原因就是把所有數據全部加載到內存了。為了避免這種情況,我們可以分批處理。

參數說明:

 

  • file:解析的文件
  • batchNumOrder:批次號
  • context:存放數據的集合
  • count:每一批次的數據量
1. 接口API@ApiOperation(value = "測試解析-數據分批版") @GetMapping("/readFileV2") public ResponseEntity readFileV2(@RequestParam(required = false) int count) { File file = new File("/Users/ajisun/projects/alwaysCoding/files/total-file.csv"); List> context = new ArrayList<>(); int batchNumOrder = 1; parseFile(file, batchNumOrder, context, count); return null; } 復制代碼2. 文件解析
文件解析,獲取文件流
private int parseFile(File file, int batchNumOrder, List> context, int count) { try ( InputStreamReader isr = new InputStreamReader(new FileInputStream(file) , StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr) ) { batchNumOrder = this.readDataFromFile(br, context, batchNumOrder, count); } catch (Exception e) { e.printStackTrace(); } return batchNumOrder; } 復制代碼3. 讀取文件數據
按行讀取文件,分割每行數據,然后按照#{count}的數量拆分,分批次存儲
private int readDataFromFile(BufferedReader br, List> context, int batchNumOrder, int count) throws IOException { String line = ""; int number = 1; while ((line = br.readLine()) != null) { //去除#號開始的行 if (!line.startsWith("#")) { if (number >= 1) { //csv是以逗號為區分的文件,以逗號區分 String[] columns = line.split(",", -1); //構建數據 context.add(constructDataMap(columns)); } number++; } if (context.size() >= count) { // TODO 存表 System.out.println("=====插入數據庫:批次:" + batchNumOrder + ",數據條數:" + context.size()); context.clear(); batchNumOrder++; } } // 最后一批次提交 if (CollectionUtils.isNotEmpty(context)) { System.out.println("=====插入數據庫:批次:" + batchNumOrder + ",數據條數:" + context.size()); context.clear(); } return batchNumOrder; } 復制代碼4.組裝數據
把每一行數據按照順序和業務對象ALI_FINANCE_LIST匹配 ,組裝成功單個map數據
public Map constructDataMap(String[] columns) { Map dataMap = new HashMap<>(16); for (int i = 0; i < columns.length; i++) { //防止異常,大于預定義的列不處理 if (i > ALI_FINANCE_LIST.size()) { break; } dataMap.put(ALI_FINANCE_LIST.get(i), columns[i].trim()); } return dataMap; } 復制代碼5.執行結果

 


 


 

把文件分批讀取插入數據庫,可以減少內存的占用以及解決高CPU的問題。已經可以很好地處理文件讀取問題了。

但是如果一個文件更大,有1G,2G 甚至更大,雖然不會造成OOM ,但是整個解析的時間就會比較長,然后如果中間出現問題,那么就需要從頭再來。

假如是1000萬數據的文件,按照一批次1萬條插入數據庫,然而到999批次的時候失敗了(不考慮回滾),那么為了保證數據的完整性,該文件就需要重新上傳解析。但實際上只需要最后一批次數據即可, 多了很多重復操作。

可以使用另一種方式處理,第三版

第三版:大而化小

分而治之,文件拆分

 

主要改動就是在第二版的基礎增加文件拆分的功能,把一個大文件按照需求拆分成n個小文件,然后單獨解析拆分后的小文件即可。其他方法不變。

1.接口API

獲取拆分后的文件,循環解析讀取
@ApiOperation(value = "測試解析-文件拆分版") @GetMapping("/readFileV3") public ResponseEntity readFileV3(@RequestParam(required = false) int count){ if (StringUtils.isEmpty(date)) { this.execCmd(); } File file = new File("/Users/ajisun/projects/alwaysCoding/files"); File[] childs = file.listFiles();//可以按照需求自行排序 for (File file1 : childs) { if (!file1.getName().contains(".csv") && file1.getName().contains("total-file-")) { file1.renameTo(new File(file1.getAbsolutePath() + ".csv")); } } int batchNumOrder = 1; List> context = new ArrayList<>(); for (File child : childs) { if (!child.getName().contains("total-file-")){ continue; } batchNumOrder = parseFile(child, batchNumOrder, context, count); } return null; } 復制代碼2.文件拆分
按照需求使用linux命令拆分文件,大而化小,然后按照一定規則命名
public List execCmd() { List msgList = new ArrayList(); String command = "cd /Users/ajisun/projects/alwaysCoding/files && split -a 2 -l 10000 total-file.csv total-file-"; try { ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", command); Process process = pb.start(); BufferedReader ir = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = ir.readLine()) != null) { msgList.add(line); } } catch (IOException e) { e.printStackTrace(); } System.out.println(msgList); return msgList; } 復制代碼

 

這種方式的處理在內存與CPU的占用和第二版基本沒有差別。

如果采用這種方式記得文件的清理,避免磁盤空間的占用

技術擴展:文件拆分cd /Users/ajisun/projects/alwaysCoding/files && split -a 2 -l 10000 total-file.csv total-file- 復制代碼

上述字符串是兩個命令用&&連接,第一個是進入到指定文件夾,第二個就是按照10000行拆分total-file.csv,而且子文件命名以total-file-開頭,后綴默認兩位字母結尾. 執行后的結果如下圖


 

 

mac下不能用數字命名(Linux下可以的),只能是默認的字母命名

 

Linux下:ajisun.log文件按照文件大小50m切割,后綴是2位數字結尾的子文件,子文件以ajisun-開頭


 

總結總結

如果確定了解析的文件都是小文件,而且文件中的數據最多也就幾萬行,那么直接簡單粗暴使用第一版也沒問題。

如果文件較大,幾十兆,或者文件中的數據有大幾十萬行,那么就使用第二版的分批處理。

如果文件很大,以G為單位,或者文件中的數據有幾百萬行,那么就使用第三版的文件拆分

這里只是做文件解析以及讀取相關的功能,但是在實際情況中可能會存在各種各樣的數據校驗,這個需要根據自己的實際情況處理,但是要避免在解析大文件的時候循環校驗,以及循環操作數據庫。必要時還可以引入中間表存儲文件數據(不做任何處理),在中間表中做數據校驗 再同步到目標表。

分享到:
標簽:文件 csv
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定