Java大集合求交集的方法比較


兩個List集合求交集想必學過Java的都知道用系統自帶的retainAll()方法,但是在數據量比較大時,這個方法效率並不高,利用空余時間研究了幾種數據量較大時求兩個集合交集的辦法。本文主要研究了JDK自帶方法求交集、Guava集合求交集、Java8的parallelStream並行流求交集、雙指針方法求交集以及bitmap求交集的方法和效率。

JDK自帶方法

最常用的求交集方法,在小數據量的時候沒什么問題,一旦兩個集合的數據量達到幾十萬級別時,效率就嚴重偏低,底層實際上也是兩個for循環加一個contains判斷,只不過JDK做了一些相應優化,不是單純O(n^2)的雙重for循環,感興趣的同學可以閱讀相應源碼。

Guava集合工具類

Guava是谷歌出的一個工具類,里面包含了很多實用的方法,求交集的方法為Sets.intersection(list, list2)實際測試下來相當高效。

Java8並行流

parallelStream()借用了Java7的Fork/Join框架,采用分治+多線程的思想來求交集

雙指針法

雙指針法又稱拉鏈法,就是先將兩個集合排序,然后借用了二路歸並排序的思想,利用兩個指針分別在兩個集合里面做標記,比較大小然后滑動,最后得到結果。

BitMap方法

將數據存進兩個bitMap中,然后進行與操作,得到最終結果,屬於一種空間換時間的方法,BitMap思想在海量數據處理中有很多妙用。

下面貼上具體實現的代碼:

package com.test.spring.learn.retainall;

import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.*;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

/**
 * Created by GeekBoy on 2020/1/4.
 */
public class RetainAllTest {
    public static void main(String[] args) {
        retainAllByGuava();
        retainAllByBitSet();
        retainAllByTwoPoint();
        retainAllByStream();
        retainAllByJDK();

    }
    /**
     * 用JDK方法求交集
     */
    private static void retainAllByJDK() {
        List<Integer> txtList = getIntegerList("e:\\a.txt");
        List<Integer> txtList2 = getIntegerList("e:\\b.txt");
        long begin = System.currentTimeMillis();
        txtList.retainAll(txtList2);
        long end = System.currentTimeMillis();
        System.out.println("JDK方法耗時:" + (end - begin));
        System.out.println("交集的個數為:" + txtList.size());
    }

    /**
     * 利用guava集合求交集
     */
    private static void retainAllByGuava() {
        List<Integer> txtList = getIntegerList("e:\\a.txt");
        List<Integer> txtList2 = getIntegerList("e:\\b.txt");
        long begin = System.currentTimeMillis();
        Set<Integer> list = new HashSet<>(txtList);
        Set<Integer> list2 = new HashSet<>(txtList2);
        Sets.SetView<Integer> intersection = Sets.intersection(list, list2);
        long end = System.currentTimeMillis();
        System.out.println("guava方法耗時:" + (end - begin));
        System.out.println("交集的個數為:" + intersection.size());
    }

    /**
     * java8 stream流求交集,實質上底層是用的多線程fork/join框架
     */
    private static void retainAllByStream() {
        List<Integer> txtList = getIntegerList("e:\\a.txt");
        List<Integer> txtList2 = getIntegerList("e:\\b.txt");
        long begin = System.currentTimeMillis();
        long count = txtList.parallelStream().
                filter(item -> txtList2.contains(item)).count();
        long end = System.currentTimeMillis();
        System.out.println("stream流求交集方法耗時:" + (end - begin));
        System.out.println("交集的個數為:" + count);
    }


    /**
     * 雙指針法求兩個list的交集
     */
    private static void retainAllByTwoPoint() {
        List<Integer> txtList = getIntegerList("e:\\a.txt");
        List<Integer> txtList2 = getIntegerList("e:\\b.txt");
        long begin = System.currentTimeMillis();
        Collections.sort(txtList);
        Collections.sort(txtList2);
        int count = 0;
        int m = 0;
        int n = 0;
        int length = txtList.size() + txtList2.size();
        for (int i = 0; i < length; i++) {
            if (m < txtList.size() && n < txtList2.size()) {
                if (txtList.get(m).equals(txtList2.get(n))) {
                    count++;
                    m++;
                    n++;
                } else if (txtList.get(m).compareTo(txtList2.get(n)) > 0) {
                    n++;
                } else {
                    m++;
                }
            } else if (m < txtList.size()) {
                if (txtList.get(m).equals(txtList2.get(n - 1))) {
                    count++;
                }
                m++;

            } else if (n < txtList2.size()) {
                if (txtList.get(m - 1).equals(txtList2.get(n))) {
                    count++;
                }
                n++;
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("雙指針方法耗時:" + (end - begin));
        System.out.println("交集的個數為:" + count);
    }

    /**
     * 利用bitmap求兩個list的交集
     */
    private static void retainAllByBitSet() {
        List<Integer> txtList = getIntegerList("e:\\a.txt");
        List<Integer> txtList2 = getIntegerList("e:\\b.txt");
        long begin = System.currentTimeMillis();
        BitSet bitSet = new BitSet(Collections.max(txtList));
        BitSet bitSet1 = new BitSet(Collections.max(txtList2));
        for (int i = 0; i < txtList.size(); i++) {
            bitSet.set(txtList.get(i));
        }
        for (int i = 0; i < txtList2.size(); i++) {
            bitSet1.set(txtList2.get(i));
        }
        bitSet.and(bitSet1);
        long end = System.currentTimeMillis();
        System.out.println("bitSet方法耗時:" + (end - begin));
        System.out.println("交集的個數為:" + bitSet.cardinality());
    }

    /**
     * 從文件讀取兩個list<Integer>
     *
     * @param filePath
     * @return
     */
    private static List<Integer> getIntegerList(String filePath) {
        InputStream inputStream = null;
        InputStreamReader is = null;
        BufferedReader br = null;
        Set<Integer> txtList = new HashSet<>();
        try {
            File txtFile = new File(filePath);
            if (txtFile.exists()) {
                inputStream = new FileInputStream(txtFile);
                is = new InputStreamReader(inputStream, "UTF-8");
                br = new BufferedReader(is);
                String str = null;
                while ((str = br.readLine()) != null) {
                    if (StringUtils.isNotBlank(str)) {
                        txtList.add(Integer.valueOf(str));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (is != null) {
                    is.close();
                }
                if (br != null) {
                    br.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return new ArrayList<>(txtList);
    }
}

最終的運行結果如下,只運行了一次,結果並不嚴謹,僅供參考:

guava方法耗時:33
交集的個數為:151695
bitSet方法耗時:25
交集的個數為:151695
雙指針方法耗時:63
交集的個數為:151695
stream流求交集方法耗時:28240
交集的個數為:151695
JDK方法耗時:91838
交集的個數為:151695

從上面的結果可以看出bieSet是最快的,guava的方法其次,JDK自帶的最慢。平時使用如果數據量不是太大用guava的工具類即可,不得不說谷歌的算法還是相當厲害的。

參考鏈接

https://blog.csdn.net/banpeng4018/article/details/101386744

鏡像地址

http://www.zhangwei.wiki/#/posts/14


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM