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其它相關文章!

推薦閱讀: