當有時候一個文件夾下有幾萬個幾十萬個文件時,咱們的桌面終端打開這個文件夾可能會卡。或者將文件進行批量上傳時,若是是在文件夾下全選,那麼基本上瀏覽器就卡死了,固然也不能這樣子操做滴~java
題主最近就遇到這樣一個問題,批量上傳文件,有幾萬個,擔憂全選會搞崩瀏覽器或者cmd終端,因而打算將數據分組,分批次上傳,減小風險壓力。可能有的同窗會說,那簡單嘛,直接ctrl C+V完事兒,可是人這個眼睛吶,越集中注意力看一個字,就越不以爲它像個字,因此不免會出錯的,並且拖動也會很卡。瀏覽器
做爲一個搞電腦的工程師(程序猿),能用電腦解決的,怎麼能浪費體力呢[滑稽]安全
其實要實現這麼一個東西,很簡單的,二話很少說,直接一個for循環搞定 歐耶!可是呀,那個速度呀,難以忍受,若是分組這個文件還須要去作一些額外的操做,那豈不是更慢,說到這,想到之前讀書學習大數據的時候,分詞計算map-reduce那每次一跑就是一個小時過去了,因此,光寫出來不得行,還得優化。並且分組的時候有一些注意點要注意。多線程
題主的大體步驟以下:工具
- 打開文件夾,遍歷文件;
- 啓用線程池,多線程跑批任務,加快速度;
- 計數文件夾中文件個數,達到指定數量,創建下一個文件夾;
- 使用新的文件夾繼續移動或複製文件,操做過程當中進行重命名;重複3,4步驟
- 操做完畢,等待線程池任務處理完畢,銷燬。
說了那麼多,仍是直接上代碼吧:)學習
import java.io.File; import java.util.Arrays; import java.util.Objects; import java.util.concurrent.*; public class TestReduceFile { /** * 文件計數 */ private static volatile int currentFileNum = 0; private static final String initFilePathName = "/Users/anhaoo/test/reduceFile/reduce"; private static volatile String currentFilePathName; public static void main(String[] args) { groupFile("/User/anhaoo/test/big_pdf"); } /** * 將文件分組:分紅一個文件夾多少個文件這種 * @param oldFilePath */ private static void groupFile(String oldFilePath) { File file = new File(oldFilePath); File[] fileList = file.listFiles(); // 線程池批處理:鏈表阻塞隊列 ExecutorService executor = Executors.newFixedThreadPool(80); if (Objects.nonNull(fileList) && fileList.length > 0) { // System.out.println(fileList.length); Arrays.stream(fileList).forEach(item -> { // 線程池提交任務處理 if (!item.isDirectory()) { executor.execute(() -> groupFileSub(item)); } }); } // shutdown 等待任務所有執行完畢 銷燬 executor.shutdown(); } /** * 分組文件,每滿n個文件生成下一個文件夾,而後將後續遍歷的文件移動到下一個文件夾中 * @param oldFile */ private static synchronized void groupFileSub(File oldFile) { // 判斷是否須要新生成文件夾,一個文件夾下放700個 if (currentFileNum % 700 == 0) { // reduce0,reduce1,reduce2...... String newFileFolder = initFilePathName.concat(String.valueOf(currentFileNum/700)); // 將新的文件夾名賦值給共享變量 currentFilePathName = newFileFolder; } File aimFilePath = new File(currentFilePathName); if (!aimFilePath.exists()) { aimFilePath.mkdirs(); } try { // 移動文件時順便重命名文件 String oldFileName = oldFile.getName(); String[] fileNameArr = oldFileName.split("_"); // 加下面這個if的緣由 是由於mac電腦下有一個隱藏的.DS_Store文件,它按個人規則重命名時會影響個人操做 // 因此判斷一下,否則我這裏會拋越界異常:) if (fileNameArr.length < 3) { return; } String uuid = fileNameArr[2]; String newFileName = "/pdfFile_".concat(uuid); File aimFile = new File(aimFilePath + File.separator + newFileName); currentFileNum++; oldFile.renameTo(aimFile); } catch (Exception e) { e.printStackTrace(); } } }
代碼就如上了,效果如圖所示:
大數據
701 是由於有一個隱藏文件了哈!優化
有幾個點須要注意一下:上面程序中使用了volatile和synchronized,以及線程池等工具,ui
可能有的同窗會有疑惑,既然使用了volatile關鍵保證了多線程變量的可見性,那爲何還要使用synchronize持鎖同步呢?哈哈哈,剛開始我也是這麼認爲的,沒有使用鎖,直接跑,可是每次跑完後每一個文件夾的數量不只不相等並且數量還不一致,不止700個多,後來仔細一想,volatile雖然保持了變量的可見性,可是當多個線程拿到這個變量是將變量副本拷貝到本身的棧內存中,只能保證在獲取變量的時候是最新的,可是CPU指令的執行是哪一個線程搶到了就去執行,因此可能恰好那個時候其餘線程又將變量修改了,由於計數變量currentFileNum在不停地自增,致使線程不安全,不符合咱們的預期效果,這個也再一次證實了一個結論:線程
volatile只是保證了可見性,可是線程變量的安全它沒法保證;
因此在這個方法上加了一個鎖,保證線程的安全性;
上述就是本文想分享的東西了,若是有更好的方法或者文章中有不足之處歡迎你們指出,共同進步纔是咱們的宗旨!