Fork/join實現Excel批量匯入去重
1.概述
最近工作中,有需要使用Fork/join 優化 的業務,本文我已實際案例出發,程式碼落地的形式來實現優化。
Fork/Join 是什麼?
Fork/Join框架是一個實現了ExecutorService介面的多執行緒處理器。它可以把一個 大的任務劃分為若干個小的任務併發執行 ,充分利用可用的資源,進而提高應用的執行效率。
2. 業務邏輯
後臺Excel批量匯入學生,匯入的時候需要與資料庫中的資料進行比較,判斷username是否重複,如果重複則不匯入。(ps:實際業務中可能是其他資料進行去重,邏輯是一樣的,本文中用學生資料作為案例)
3.已知
資料庫 student表 10w條資料
excel中 1w條資料
3.思路
3.1 第一版本思路
為了減少資料庫的壓力,我們
第一步把所有資料讀取到Java記憶體中


第二步 excel 資料 本身去重
第三步 遍歷excel資料,每一個username拿去與dbData判斷是否有相等的
在 資料庫10w excel 1w 條的資料情況下 篩選耗時 8049ms
3.2 第二步思路(加入Fork/join)
結合業務,我的思路是選擇在上述的第三步優化,把之前的 1w條資料篩選 拆分 成2個 5000的任務 同時 進行
這樣在其他因素不變的情況下 ,第三步篩選的 效率會提高大概一倍
實現
第一步 編寫一個有返回值的任務類,定義好
THRESHOLD_NUM (單個執行緒處理資料個數)
start ,end (下標)
第二步 在 compute 實現 裡面實現 邏輯 與 任務的拆分
當處理資料小於等於 單個執行緒處理的資料值 ,則進行去重的業務
當處理資料小於等於 單個執行緒處理的資料值 ,則需要對任務的拆分與結果集的合併(ps:遞迴呼叫)
第三步業務層呼叫
在 資料庫10w excel 1w 條的資料情況下 篩選耗時 4319ms
效率提高接近一倍
4.程式碼
package com.itbbs.service; import com.itbbs.ArrayListUtil; import com.itbbs.DataUtil; import com.itbbs.pojo.Student; import com.itbbs.task.DistinctTask; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; /** * 學生操作service * tjx */ public class StudentService { /** * 批量匯入學生 */ public void importStudent(List<Student> dbData,List<Student> excelData){ // excel自身去重 excelData = excelData.stream() .filter(ArrayListUtil.distinctByKey(Student::getUsername)) //根據username 去重 .collect(Collectors.toList()); //不重複資料 List<Student> repetitionData = new ArrayList<>(); long s = System.currentTimeMillis(); //遍歷所以excel資料 for (Student data : excelData) { String username = data.getUsername(); //判斷是否存在於 dbData中 if (!ArrayListUtil.isInclude(dbData, username)) { //如果不存在則新增到不重複集合中 repetitionData.add(data); } } long e = System.currentTimeMillis(); System.out.println("篩選耗時:"+(e-s)+"ms"); //在資料庫不重複的資料裡面 篩選出不重複資料 repetitionData =repetitionData.stream() .sorted(Comparator.comparing(Student::getUsername))//根據username 排序 .collect(Collectors.toList()); //repetitionData.forEach(p-> System.out.println(p.getUsername())); } /** * 批量匯入學生 */ public void importStudent2(List<Student> dbData,List<Student> excelData){ // 自身去重 excelData = excelData.stream() .filter(ArrayListUtil.distinctByKey(Student::getUsername)) //根據username 去重 .collect(Collectors.toList()); long s = System.currentTimeMillis(); //獲取不重複資料 ForkJoinPool fjp = new ForkJoinPool(); DistinctTask task= new DistinctTask(0,excelData.size(),dbData,excelData); List<Student> repetitionData = fjp.invoke(task); long e = System.currentTimeMillis(); System.out.println("篩選耗時:"+(e-s)+"ms"); //在資料庫不重複的資料裡面 篩選出不重複資料 repetitionData =repetitionData.stream() .sorted(Comparator.comparing(Student::getUsername))//根據username 排序 .collect(Collectors.toList()); //repetitionData.forEach(p-> System.out.println(p.getUsername())); } public static void main(String[] args) { // 模擬獲取資料庫 List<Student> dbData = DataUtil.getDbData(); // 模擬獲取excel資料 List<Student> excelData = DataUtil.getExcelData(); new StudentService().importStudent(dbData,excelData); new StudentService().importStudent2(dbData,excelData); } } 複製程式碼
package com.itbbs.task; import com.itbbs.ArrayListUtil; import com.itbbs.pojo.Student; import java.util.ArrayList; import java.util.List; import java.util.concurrent.RecursiveTask; public class DistinctTask extends RecursiveTask<List<Student>> { //單個任務處理資料 private static final int THRESHOLD_NUM = 5000; //下標 private int start, end; //需要處理的資料 private List<Student> dbData; private List<Student> excelData; public DistinctTask(int start, int end, List<Student> dbData, List<Student> excelData) { this.start = start; this.end = end; this.dbData = dbData; this.excelData = excelData; } @Override protected List<Student> compute() { //獲取當前下標下的資料 excelData = excelData.subList(start,end); //獲取需要計算的資料量 int size = excelData.size(); if(size<=THRESHOLD_NUM){ //計算 List<Student> repetitionData = new ArrayList<>(); //遍歷所以excel資料 for (Student data : excelData) { String username = data.getUsername(); //判斷是否存在於 dbData中 if (!ArrayListUtil.isInclude(dbData, username)) { //如果不存在則新增到不重複集合中 repetitionData.add(data); } } return repetitionData; }else{ //拆分 int middle = (start + end) / 2; DistinctTask left = new DistinctTask(start,middle,dbData,excelData); DistinctTask right = new DistinctTask(middle+1,end,dbData,excelData); //執行子任務 left.fork(); right.fork(); //獲取子任務結果 //join() 方法會阻塞到結果算出來 List<Student> lResult = left.join(); List<Student> rResult = right.join(); //何並結果 lResult.addAll(rResult); return lResult; } } } 複製程式碼
package com.itbbs; import com.itbbs.pojo.Student; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * 資料來源 工具類 * tjx */ public class DataUtil { /** * 模擬 獲取資料庫 * @return */ public static List<Student> getDbData(){ List<Student> result = new ArrayList<Student>(); Random random = new Random(); for (int i = 0; i <100000 ; i++) { Student student = new Student(); student.setUsername(random.nextInt(99)+""); result.add(student); } return result; } /** * 模擬 獲取excel資料 * @return */ public static List<Student> getExcelData(){ List<Student> result = new ArrayList<Student>(); Random random = new Random(); for (int i = 0; i <10000 ; i++) { Student student = new Student(); student.setUsername(random.nextInt(100000)+""); result.add(student); } return result; } } 複製程式碼
package com.itbbs; import com.itbbs.pojo.Student; import org.apache.commons.lang3.ArrayUtils; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; public class ArrayListUtil { /** * 去重複元素 * @param keyExtractor * @param <T> * @return */public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) { Map<Object, Boolean> map = new ConcurrentHashMap<>();return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; } /** * 判斷 list 是否包含 targetValue * @param list * @param targetValue * @return */ public static boolean isInclude(List<Student> list, String targetValue){ returnArrayUtils.contains(list.toArray(),targetValue); } } 複製程式碼
package com.itbbs.pojo; public class Student { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public boolean equals(Object obj) { if (obj instanceof Student) { Student student = (Student) obj; return (username.equals(student.username)); } return super.equals(obj); } @Override public int hashCode() { Student student = (Student) this; return student.username.hashCode(); } } 複製程式碼