Java找出兩個大數據量List集合中的不同元素的方法總結
本文將帶你瞭解如何快速的找出兩個相似度非常高的List集合裡的不同元素。主要通過Java API、List集合雙層遍歷比較不同、借助Map集合查找三種方式,以及他們之間的執行效率情況,話不多說,開搞! 集合初始化方法:
/** * 制造任意個元素的的List集合 * @param size List集合的size * @return List<String> */ private static List<String> dataList(int size) { List<String> dataList = new ArrayList<>(); for (int i = 0; i < size; i++) { dataList.add("" + i); } return dataList; }
測試數據為集合A: 1千, 1萬, 10萬,1百萬, 1千萬的數據量.
- 集合B比集合A多初始化六條數據,集合A添加一條特有的數據。
- 測試數據使用空字符串 + 自然數的方式。
JavaAPI過濾(不推薦)
1千數據量
List<String> listA = dataList(1000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1006); Long startTime = System.currentTimeMillis(); // 復制集合A和集合B作為備份 List<String> listABak = new ArrayList<>(listA); List<String> listBBak = new ArrayList<>(listB); // 集合B存在,集合A不存在的元素 listB.removeAll(listA); // 集合A存在,集合B不存在的元素 listA.removeAll(listBBak); Long endTime = System.currentTimeMillis(); List<String> differentList = new ArrayList<>(); differentList.addAll(listB); differentList.addAll(listA); System.out.println("集合A和集合B不同的元素:"+differentList); Long costTime = endTime-startTime; System.out.println("比對耗時:"+costTime+"毫秒。");
耗時:22毫秒
1萬數據量
List<String> listA = dataList(10000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(10006); Long startTime = System.currentTimeMillis(); // 復制集合A和集合B作為備份 List<String> listABak = new ArrayList<>(listA); List<String> listBBak = new ArrayList<>(listB); // 集合B存在,集合A不存在的元素 listB.removeAll(listA); // 集合A存在,集合B不存在的元素 listA.removeAll(listBBak); Long endTime = System.currentTimeMillis(); List<String> differentList = new ArrayList<>(); differentList.addAll(listB); differentList.addAll(listA); System.out.println("集合A和集合B不同的元素:"+differentList); Long costTime = endTime-startTime; System.out.println("比對耗時:"+costTime+"毫秒。");
耗時:613毫秒
10萬數據量
List<String> listA = dataList(100000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(100006); Long startTime = System.currentTimeMillis(); // 復制集合A和集合B作為備份 List<String> listABak = new ArrayList<>(listA); List<String> listBBak = new ArrayList<>(listB); // 集合B存在,集合A不存在的元素 listB.removeAll(listA); // 集合A存在,集合B不存在的元素 listA.removeAll(listBBak); Long endTime = System.currentTimeMillis(); List<String> differentList = new ArrayList<>(); differentList.addAll(listB); differentList.addAll(listA); System.out.println("集合A和集合B不同的元素:"+differentList); Long costTime = endTime-startTime; System.out.println("比對耗時:"+costTime+"毫秒。");
可以看出來十萬數據量級別已經比較慢瞭,需要77秒
100萬數據量
emmm估計挺慢,不繼續驗證瞭。
為什麼在數據量增大的時候,這種方法性能下降的這麼明顯? 我們不妨來看一下removeAll的源碼:
public boolean removeAll(Collection<?> c) { // 先判空,然後執行批量remove Objects.requireNonNull(c); return batchRemove(c, false); }
通過源碼我們可以看到,該方法是使用for循環對集合進行遍歷 第一層循環需要執行 listA.size()次,裡面調用瞭contains方法來確定集合B是否含有該元素, 再看contains方法的源碼:
可以看到,indexOf方法裡又進行瞭一層遍歷. 平均每次遍歷要進行list.size() / 2次計算, 假設集合A的元素個數為m,集合B的元素個數為n 我們可以得到結論,運算次數為 m *(n/2) 對於100W數據量來說,假設你的計算機每秒能夠執行1千萬次運算,也需要13.8個小時才能對比出來。所以大數據量不建議通過此方法。
List集合雙層遍歷比較不同(不推薦)
該方法實際上就是將removeAll的實現邏輯用自己的方式寫出來,所以執行效率,運行結果和上一種方法沒什麼區別,這裡隻貼代碼出來,不再贅述。
private static void doubleFor() { List<String> listA = dataList(1000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1006); System.out.println("數量級為 " + listA.size() + "集合的不同元素為"); List<String> differentList = new ArrayList<>(); long startTime = System.currentTimeMillis(); for (String str : listB) { if (!listA.contains(str)) { differentList.add(str); } } for (String str : listA) { if (!listB.contains(str)) { differentList.add(str); } } long endTime = System.currentTimeMillis(); System.out.println("集合A和集合B不同的元素:"+differentList); System.out.println("使用雙層遍歷方法 比對耗時: " + (endTime - startTime)+"毫秒。"); }
以上兩個方法中我都做瞭m*n次循環,其實完全沒有必要循環這麼多次,我們的需求是找出兩個List中的不同元素,那麼我可以這樣考慮:用一個map存放lsit的所有元素,其中的key為lsit1的各個元素,value為該元素出現的次數,接著把list2的所有元素也放到map裡,如果已經存在則value加1,最後我們隻要取出map裡value為1的元素即可,這樣我們隻需循環m+n次,大大減少瞭循環的次數。
借助Map集合查找(推薦)
以List集合裡的元素作為Map的key,元素出現的次數作為Map的Value,那麼兩個List集合的不同元素在Map集合中value值為1,所對應的鍵。把所有value值為1的鍵找出來,我們就得到瞭兩個List集合不同的元素。 代碼如下:
/** * 借助Map來獲取listA、listB的不同元素集合 * * @param listA 集合A * @param listB 集合B * @return list<String> 不同元素集合 */ public static List<String> getDifferListByMap(List<String> listA, List<String> listB) { List<String> differList = new ArrayList<>(); Map<String, Integer> map = new HashMap<>(); long beginTime = System.currentTimeMillis(); for (String strA : listA) { map.put(strA, 1); } for (String strB : listB) { Integer value = map.get(strB); if (value != null) { map.put(strB, ++value); continue; } map.put(strB, 1); } for (Map.Entry<String, Integer> entry : map.entrySet()) { //獲取不同元素集合 if (entry.getValue() == 1) { differList.add(entry.getKey()); } } long endTime = System.currentTimeMillis(); System.out.println("集合A和集合B不同的元素:"+differList); System.out.println("使用map方式遍歷, 對比耗時: " + (endTime - beginTime)+"毫秒。"); return differList; }
1千數據量
List<String> listA = dataList(1000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1006); getDifferListByMap(listA,listB);
耗時:7毫秒
1萬數據量
List<String> listA = dataList(10000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(10006); getDifferListByMap(listA,listB);
耗時:42毫秒
10萬數據量
List<String> listA = dataList(100000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(100006); getDifferListByMap(listA,listB);
耗時:130毫秒
100萬數據量
List<String> listA = dataList(1000000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1000006); getDifferListByMap(listA,listB);
耗時:283毫秒
1000萬數據量
List<String> listA = dataList(10000000); //集合A添加一個集合B沒有的元素 listA.add("onlyA10086"); List<String> listB = dataList(10000006); getDifferListByMap(listA,listB);
耗時:6.6秒
可以看出來這種方法相當高效,千萬級數據比對也才用瞭6.6秒。 使用map集合的方式尋找不同元素,時間增長基本上是線性的,它的時間復雜度為O(m)。 而上面的remove方式和雙層循環遍歷的時間復雜度為O(m * n)。 所以,選用這種方式帶來的性能收益隨著集合元素的增長而增長。
優化
上述通過map集合的方式效率很高,有沒有可以優化的點呢?
- 兩個集合如果數量差距較大時,可以把小的在後面添加,這樣會減少循環裡的判斷,性能又有瞭一定的提升。
- 創建map集合的時候直接指定大小,防止再散列。
優化後代碼如下:
/** * 找出兩個集合中不同的元素 * * @param collmax * @param collmin * @return */ public static Collection getDifferListByMapPlus(Collection collmax, Collection collmin) { //使用LinkedList防止差異過大時,元素拷貝 Collection csReturn = new LinkedList(); Collection max = collmax; Collection min = collmin; long beginTime = System.currentTimeMillis(); //先比較大小,這樣會減少後續map的if判斷次數 if (collmax.size() < collmin.size()) { max = collmin; min = collmax; } //直接指定大小,防止再散列 Map<Object, Integer> map = new HashMap<Object, Integer>(max.size()); for (Object object : max) { map.put(object, 1); } for (Object object : min) { if (map.get(object) == null) { csReturn.add(object); } else { map.put(object, 2); } } for (Map.Entry<Object, Integer> entry : map.entrySet()) { if (entry.getValue() == 1) { csReturn.add(entry.getKey()); } } long endTime = System.currentTimeMillis(); System.out.println("集合A和集合B不同的元素:"+csReturn); System.out.println("使用map方式遍歷, 對比耗時: " + (endTime - beginTime)+"毫秒。"); return csReturn; }
找出相同元素
同樣找出相同元素可以使用如下代碼:
/** * 找出兩個集合中相同的元素 * * @param collmax * @param collmin * @return */ public static Collection getSameListByMap(Collection collmax, Collection collmin) { //使用LinkedList防止差異過大時,元素拷貝 Collection csReturn = new LinkedList(); Collection max = collmax; Collection min = collmin; //先比較大小,這樣會減少後續map的if判斷次數 if (collmax.size() < collmin.size()) { max = collmin; min = collmax; } //直接指定大小,防止再散列 Map<Object, Integer> map = new HashMap<Object, Integer>(max.size()); for (Object object : max) { map.put(object, 1); } for (Object object : min) { if (map.get(object) != null) { csReturn.add(object); } } return csReturn; }
以上就是Java找出兩個大數據量List集合中的不同元素的方法總結的詳細內容,更多關於Java找出List集合中的不同元素的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 大數組元素差異removeAll與Map效率對比
- 一篇文章帶你瞭解Java泛型的super和extends
- Java8 ArrayList之forEach的使用
- Java基礎之List內元素的排序性能對比
- 如何將Object類轉換為實體類