Java找出兩個(gè)大數(shù)據(jù)量List集合中的不同元素的方法總結(jié)
本文將帶你了解如何快速的找出兩個(gè)相似度非常高的List集合里的不同元素。主要通過(guò)Java API、List集合雙層遍歷比較不同、借助Map集合查找三種方式,以及他們之間的執(zhí)行效率情況,話不多說(shuō),開(kāi)搞! 集合初始化方法:
/** * 制造任意個(gè)元素的的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; }
測(cè)試數(shù)據(jù)為集合A: 1千, 1萬(wàn), 10萬(wàn),1百萬(wàn), 1千萬(wàn)的數(shù)據(jù)量.
- 集合B比集合A多初始化六條數(shù)據(jù),集合A添加一條特有的數(shù)據(jù)。
- 測(cè)試數(shù)據(jù)使用空字符串 + 自然數(shù)的方式。
JavaAPI過(guò)濾(不推薦)
1千數(shù)據(jù)量
List<String> listA = dataList(1000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1006); Long startTime = System.currentTimeMillis(); // 復(fù)制集合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("比對(duì)耗時(shí):"+costTime+"毫秒。");
耗時(shí):22毫秒
1萬(wàn)數(shù)據(jù)量
List<String> listA = dataList(10000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(10006); Long startTime = System.currentTimeMillis(); // 復(fù)制集合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("比對(duì)耗時(shí):"+costTime+"毫秒。");
耗時(shí):613毫秒
10萬(wàn)數(shù)據(jù)量
List<String> listA = dataList(100000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(100006); Long startTime = System.currentTimeMillis(); // 復(fù)制集合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("比對(duì)耗時(shí):"+costTime+"毫秒。");
可以看出來(lái)十萬(wàn)數(shù)據(jù)量級(jí)別已經(jīng)比較慢了,需要77秒
100萬(wàn)數(shù)據(jù)量
emmm估計(jì)挺慢,不繼續(xù)驗(yàn)證了。
為什么在數(shù)據(jù)量增大的時(shí)候,這種方法性能下降的這么明顯? 我們不妨來(lái)看一下removeAll的源碼:
public boolean removeAll(Collection<?> c) { // 先判空,然后執(zhí)行批量remove Objects.requireNonNull(c); return batchRemove(c, false); }
通過(guò)源碼我們可以看到,該方法是使用for循環(huán)對(duì)集合進(jìn)行遍歷 第一層循環(huán)需要執(zhí)行 listA.size()次,里面調(diào)用了contains方法來(lái)確定集合B是否含有該元素, 再看contains方法的源碼:
可以看到,indexOf方法里又進(jìn)行了一層遍歷. 平均每次遍歷要進(jìn)行l(wèi)ist.size() / 2次計(jì)算, 假設(shè)集合A的元素個(gè)數(shù)為m,集合B的元素個(gè)數(shù)為n 我們可以得到結(jié)論,運(yùn)算次數(shù)為 m *(n/2) 對(duì)于100W數(shù)據(jù)量來(lái)說(shuō),假設(shè)你的計(jì)算機(jī)每秒能夠執(zhí)行1千萬(wàn)次運(yùn)算,也需要13.8個(gè)小時(shí)才能對(duì)比出來(lái)。所以大數(shù)據(jù)量不建議通過(guò)此方法。
List集合雙層遍歷比較不同(不推薦)
該方法實(shí)際上就是將removeAll的實(shí)現(xiàn)邏輯用自己的方式寫出來(lái),所以執(zhí)行效率,運(yùn)行結(jié)果和上一種方法沒(méi)什么區(qū)別,這里只貼代碼出來(lái),不再贅述。
private static void doubleFor() { List<String> listA = dataList(1000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1006); System.out.println("數(shù)量級(jí)為 " + 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("使用雙層遍歷方法 比對(duì)耗時(shí): " + (endTime - startTime)+"毫秒。"); }
以上兩個(gè)方法中我都做了m*n次循環(huán),其實(shí)完全沒(méi)有必要循環(huán)這么多次,我們的需求是找出兩個(gè)List中的不同元素,那么我可以這樣考慮:用一個(gè)map存放lsit的所有元素,其中的key為lsit1的各個(gè)元素,value為該元素出現(xiàn)的次數(shù),接著把list2的所有元素也放到map里,如果已經(jīng)存在則value加1,最后我們只要取出map里value為1的元素即可,這樣我們只需循環(huán)m+n次,大大減少了循環(huán)的次數(shù)。
借助Map集合查找(推薦)
以List集合里的元素作為Map的key,元素出現(xiàn)的次數(shù)作為Map的Value,那么兩個(gè)List集合的不同元素在Map集合中value值為1,所對(duì)應(yīng)的鍵。把所有value值為1的鍵找出來(lái),我們就得到了兩個(gè)List集合不同的元素。 代碼如下:
/** * 借助Map來(lái)獲取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方式遍歷, 對(duì)比耗時(shí): " + (endTime - beginTime)+"毫秒。"); return differList; }
1千數(shù)據(jù)量
List<String> listA = dataList(1000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1006); getDifferListByMap(listA,listB);
耗時(shí):7毫秒
1萬(wàn)數(shù)據(jù)量
List<String> listA = dataList(10000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(10006); getDifferListByMap(listA,listB);
耗時(shí):42毫秒
10萬(wàn)數(shù)據(jù)量
List<String> listA = dataList(100000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(100006); getDifferListByMap(listA,listB);
耗時(shí):130毫秒
100萬(wàn)數(shù)據(jù)量
List<String> listA = dataList(1000000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(1000006); getDifferListByMap(listA,listB);
耗時(shí):283毫秒
1000萬(wàn)數(shù)據(jù)量
List<String> listA = dataList(10000000); //集合A添加一個(gè)集合B沒(méi)有的元素 listA.add("onlyA10086"); List<String> listB = dataList(10000006); getDifferListByMap(listA,listB);
耗時(shí):6.6秒
可以看出來(lái)這種方法相當(dāng)高效,千萬(wàn)級(jí)數(shù)據(jù)比對(duì)也才用了6.6秒。 使用map集合的方式尋找不同元素,時(shí)間增長(zhǎng)基本上是線性的,它的時(shí)間復(fù)雜度為O(m)。 而上面的remove方式和雙層循環(huán)遍歷的時(shí)間復(fù)雜度為O(m * n)。 所以,選用這種方式帶來(lái)的性能收益隨著集合元素的增長(zhǎng)而增長(zhǎng)。
優(yōu)化
上述通過(guò)map集合的方式效率很高,有沒(méi)有可以優(yōu)化的點(diǎn)呢?
- 兩個(gè)集合如果數(shù)量差距較大時(shí),可以把小的在后面添加,這樣會(huì)減少循環(huán)里的判斷,性能又有了一定的提升。
- 創(chuàng)建map集合的時(shí)候直接指定大小,防止再散列。
優(yōu)化后代碼如下:
/** * 找出兩個(gè)集合中不同的元素 * * @param collmax * @param collmin * @return */ public static Collection getDifferListByMapPlus(Collection collmax, Collection collmin) { //使用LinkedList防止差異過(guò)大時(shí),元素拷貝 Collection csReturn = new LinkedList(); Collection max = collmax; Collection min = collmin; long beginTime = System.currentTimeMillis(); //先比較大小,這樣會(huì)減少后續(xù)map的if判斷次數(shù) 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方式遍歷, 對(duì)比耗時(shí): " + (endTime - beginTime)+"毫秒。"); return csReturn; }
找出相同元素
同樣找出相同元素可以使用如下代碼:
/** * 找出兩個(gè)集合中相同的元素 * * @param collmax * @param collmin * @return */ public static Collection getSameListByMap(Collection collmax, Collection collmin) { //使用LinkedList防止差異過(guò)大時(shí),元素拷貝 Collection csReturn = new LinkedList(); Collection max = collmax; Collection min = collmin; //先比較大小,這樣會(huì)減少后續(xù)map的if判斷次數(shù) 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找出兩個(gè)大數(shù)據(jù)量List集合中的不同元素的方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java找出List集合中的不同元素的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java調(diào)用構(gòu)造函數(shù)和方法及使用詳解
在Java編程中,構(gòu)造函數(shù)用于初始化新創(chuàng)建的對(duì)象,而方法則用于執(zhí)行對(duì)象的行為,構(gòu)造函數(shù)在使用new關(guān)鍵字創(chuàng)建類實(shí)例時(shí)自動(dòng)調(diào)用,沒(méi)有返回類型,并且名稱與類名相同,本文通過(guò)示例詳細(xì)介紹了如何在Java中使用構(gòu)造函數(shù)和方法,感興趣的朋友一起看看吧2024-10-10Docker?DockerFile部署java?jar項(xiàng)目包及Mysql和Redis的詳細(xì)過(guò)程
Dockerfile是一種用于構(gòu)建Docker鏡像的文件格式,可以通過(guò)Dockerfile部署Java項(xiàng)目,這篇文章主要給大家介紹了關(guān)于Docker?DockerFile部署java?jar項(xiàng)目包及Mysql和Redis的詳細(xì)過(guò)程,需要的朋友可以參考下2023-12-12基于html5+java實(shí)現(xiàn)大文件上傳實(shí)例代碼
本文通過(guò)一段實(shí)例代碼給大家介紹基于html5+java實(shí)現(xiàn)大文件上傳,涉及到html5 java 文件上傳相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧2016-01-01java實(shí)現(xiàn)網(wǎng)頁(yè)解析示例
這篇文章主要介紹了java實(shí)現(xiàn)網(wǎng)頁(yè)解析示例,需要的朋友可以參考下2014-04-04Java實(shí)現(xiàn)中文字符串與unicode互轉(zhuǎn)工具類
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)中文字符串與unicode互轉(zhuǎn)的工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04SpringBoot基于SpringSecurity表單登錄和權(quán)限驗(yàn)證的示例
這篇文章主要介紹了SpringBoot基于SpringSecurity表單登錄和權(quán)限驗(yàn)證的示例。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09SpringBoot概述及在idea中創(chuàng)建方式
SpringBoot提供了一種快速使用Spring的方式,基于約定大于配置的思想,可以讓開(kāi)發(fā)人員不必在配置與邏輯業(yè)務(wù)之間進(jìn)行思維的切換,這篇文章主要介紹了SpringBoot概述及在idea中創(chuàng)建方式,需要的朋友可以參考下2022-09-09java基礎(chǔ)之初始化ArrayList時(shí)直接賦值的4種方式總結(jié)
ArrayList是Java中的一個(gè)類,它是Java集合框架中的一部分,用于實(shí)現(xiàn)動(dòng)態(tài)數(shù)組,下面這篇文章主要給大家介紹了關(guān)于java基礎(chǔ)之初始化ArrayList時(shí)直接賦值的4種方式,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07