Java高效找出两个大数据量List集合中的不同元素

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 本文将带你了解如何快速的找出两个相似度非常高的List集合里的不同元素。主要通过Java API、List集合双层遍历比较不同、借助Map集合查找三种方式,以及他们之间的执行效率情况。

本文将带你了解如何快速的找出两个相似度非常高的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毫秒
image.png

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毫秒
image.png

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+"毫秒。");

image.png
可以看出来十万数据量级别已经比较慢了,需要77秒

100万数据量

emmm估计挺慢,不继续验证了。😂😂😂

为什么在数据量增大的时候,这种方法性能下降的这么明显?
我们不妨来看一下removeAll的源码:

    public boolean removeAll(Collection<?> c) {
        // 先判空,然后执行批量remove
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

image.png
通过源码我们可以看到,该方法是使用for循环对集合进行遍历
第一层循环需要执行 listA.size()次,里面调用了contains方法来确定集合B是否含有该元素,
再看contains方法的源码:
image.png
可以看到,indexOf方法里又进行了一层遍历.
平均每次遍历要进行list.size() / 2次计算,
假设集合A的元素个数为m,集合B的元素个数为n
我们可以得到结论,运算次数为 m *(n/2)
对于100W数据量来说,假设你的计算机每秒能够执行1千万次运算,也需要13.8个小时才能对比出来。所以大数据量不建议通过此方法
image.png

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毫秒
image.png

1万数据量

        List<String> listA = dataList(10000);
        //集合A添加一个集合B没有的元素
        listA.add("onlyA10086");
        List<String> listB = dataList(10006);
        getDifferListByMap(listA,listB);

耗时:42毫秒
image.png

10万数据量

        List<String> listA = dataList(100000);
        //集合A添加一个集合B没有的元素
        listA.add("onlyA10086");
        List<String> listB = dataList(100006);
        getDifferListByMap(listA,listB);

耗时:130毫秒
image.png

100万数据量

        List<String> listA = dataList(1000000);
        //集合A添加一个集合B没有的元素
        listA.add("onlyA10086");
        List<String> listB = dataList(1000006);
        getDifferListByMap(listA,listB);

耗时:283毫秒
image.png

1000万数据量

        List<String> listA = dataList(10000000);
        //集合A添加一个集合B没有的元素
        listA.add("onlyA10086");
        List<String> listB = dataList(10000006);
        getDifferListByMap(listA,listB);

耗时:6.6秒
image.png
可以看出来这种方法相当高效,千万级数据比对也才用了6.6秒。
使用map集合的方式寻找不同元素,时间增长基本上是线性的,它的时间复杂度为O(m)。
而上面的remove方式和双层循环遍历的时间复杂度为O(m * n)。
所以,选用这种方式带来的性能收益随着集合元素的增长而增长。

优化

上述通过map集合的方式效率很高,有没有可以优化的点呢?

  1. 两个集合如果数量差距较大时,可以把小的在后面添加,这样会减少循环里的判断,性能又有了一定的提升。
  2. 创建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;
    }
相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
相关文章
|
1月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
2月前
|
消息中间件 算法 安全
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
17天前
|
存储 安全 算法
Java 集合面试题 PDF 下载及高频考点解析
本文围绕Java集合面试题展开,详细解析了集合框架的基本概念、常见集合类的特点与应用场景。内容涵盖`ArrayList`与`LinkedList`的区别、`HashSet`与`TreeSet`的对比、`HashMap`与`ConcurrentHashMap`的线程安全性分析等。通过技术方案与应用实例,帮助读者深入理解集合类的特性和使用场景,提升解决实际开发问题的能力。文末附带资源链接,供进一步学习参考。
29 4
|
20天前
|
存储 安全 Java
现代应用场景中 Java 集合框架的核心技术与实践要点
本内容聚焦Java 17及最新技术趋势,通过实例解析Java集合框架的高级用法与性能优化。涵盖Record类简化数据模型、集合工厂方法创建不可变集合、HashMap初始容量调优、ConcurrentHashMap高效并发处理、Stream API复杂数据操作与并行流、TreeMap自定义排序等核心知识点。同时引入JMH微基准测试与VisualVM工具分析性能,总结现代集合框架最佳实践,如泛型使用、合适集合类型选择及线程安全策略。结合实际案例,助你深入掌握Java集合框架的高效应用与优化技巧。
42 4
|
20天前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
76 3
|
3月前
|
人工智能 Java
Java 中数组Array和列表List的转换
本文介绍了数组与列表之间的相互转换方法,主要包括三部分:1)使用`Collections.addAll()`方法将数组转为列表,适用于引用类型,效率较高;2)通过`new ArrayList&lt;&gt;()`构造器结合`Arrays.asList()`实现类似功能;3)利用JDK8的`Stream`流式计算,支持基本数据类型数组的转换。此外,还详细讲解了列表转数组的方法,如借助`Stream`实现不同类型数组间的转换,并附带代码示例与执行结果,帮助读者深入理解两种数据结构的互转技巧。
Java 中数组Array和列表List的转换
|
2月前
|
Java
Java LinkedList集合的深度剖析
总的来说,我希望像说故事一样讲解Java LinkedList集合的使用和实现原理,让有些许枯燥的编程知识变得趣味盎然。在这个“公交车”故事中,你不仅熟悉了LinkedList集合的实现和使用,而且还更深入地理解了数据结构中的链表。链表可能会因为插入和删除的便利性而被选用,虽然它的查找效率并不高,但是在很多场景中仍然十分有效。这就像公交车,虽然它速度不快,但却是城市出行的重要工具。
67 8
|
2月前
|
存储 安全 Java
Java 集合框架详解:系统化分析与高级应用
本文深入解析Java集合框架,涵盖List、Set、Map等核心接口及其常见实现类,如ArrayList、HashSet、HashMap等。通过对比不同集合类型的特性与应用场景,帮助开发者选择最优方案。同时介绍Iterator迭代机制、Collections工具类及Stream API等高级功能,提升代码效率与可维护性。适合初学者与进阶开发者系统学习与实践。
83 0
|
安全 Java
Day4-如何删除Java集合中的元素(安全与不安全的删除方式详解!)
在删除Java集合中的元素时有会出现安全删除和不安全删除,本案例以list集合为例,list集合的特点:元素有序、可以出现重复的元素。
Day4-如何删除Java集合中的元素(安全与不安全的删除方式详解!)