• 还在用双层for循环吗?太慢了


    前情提要

    我们在开发中经常碰到这样的场景,查出两个 list 集合数据,需要根据他们相同的某个属性为连接点,进行聚合。但是平时我们使用的时候关注过性能吗?下面让我们一起来看看它的表现如何。

    来个例子

    我们现在有两个 List集合,需要根据他们相同的 personId 进行聚合处理,我们很容易想到的写法是这样的:

    private static void test1(List list1, List list2) {
        for (Person before:list1){
            for (Person after:list2){
                if(before.getPersonId().equals(after.getPersonId())){
                   //TODO 业务逻辑
                    break;
                }
            }
        }
    }
    

    这样的代码是我们开发中最常用的一种方式,数据少的话没问题。如果数据量大的会很慢,接下来我做一个实验。看看在 1w 和 10w 的数据量下他的性能如何?

    测试代码如下:

       public static void main(String[] args) {
            List list1= new ArrayList<>();
            List list2= new ArrayList<>();
            for (int i = 0; i < 10_0000; i++) {
                list1.add(Person.builder().personId(Long.valueOf(i+"")).build());
                list2.add(Person.builder().personId(Long.valueOf(i+"")).build());
            }
            long start = System.currentTimeMillis();
            test1(list1, list2);
            System.out.println("for循环耗时:"+(System.currentTimeMillis()-start));
    
    

    1w 耗时:343

    10w 耗时:64285

    仅仅 10w 的数据竟然达到了 64 秒多,可以看出它的性能是多么差了吧。

    那怎么优化呢?我们可以把第二个 list 转为 map 的方式来做,示例如下:

    代码如下:

    private static void test2(List list1, List list2) {
        Map<Long, Person> baseMap =
                list2.stream().collect(Collectors.toMap(Person::getPersonId, Function.identity()));
        for (Person before:list1){
            Person after = baseMap.get(before.getPersonId());
    
        }
    }
    

    接下来我们再进行下性能测试。

    1w 耗时:88

    10w 耗时:95

    可以看出速度快了上百倍不止,如果还有小伙伴用第一种方式的话就赶紧优化了吧。

    思考

    我们想想第一种为什么会慢呢?

    在第二个循环里他需要从 0 开始遍历所有的元素来进行比对,数据量越大,它需要遍历的数就越多,所以很慢。

    所以如果我们业务上两个集合的大小和顺序一致(即能知道应该第二个循环能匹配上的元素在第几个),那么就能避免掉大量的循环。

    示例如下:

    我们直接在第二层循环的时候,将下标先指定为和第一层循环的一致,如果他们俩属性相同,立马跳出;进行第二次循环。

    private static void test3(List list1, List list2) {
        for (int i=0;iint jj = 0;
            for (int j = i; j < list2.size(); j++) {
                if (jj == list2.size()) {
                    break;
                }
    
                if(list1.get(i).getPersonId().equals(list2.get(j).getPersonId())){
                    // 编写具体的逻辑
                    break;
                }
                if (j == list2.size() - 1) j = -1;
                jj += 1;
            }
        }
    }
    

    性能测试如下:

    1w 耗时:2

    10w 耗时:13

    我们发现又更加快了。

    下面是总体的测试数据:

    数据量 双层 for 循环 循环+map 改良版 for 循环
    100 条数据 1 毫秒 70 毫秒 <1 毫秒
    1000 条数据 16 毫秒 91 毫秒 1 毫秒
    5000 条数据 66 毫秒 66 毫秒 3 毫秒
    1w 条数据 208 毫秒 64 毫秒 4 毫秒
    10w 条数据 62887 毫秒 84 毫秒 17 毫秒
    100w 条数据 很久 155 毫秒 24毫秒

    总结:如果数据量小于 5000,推荐就用双层 for 循环,如果大于 5000,则使用循环+map 的方式。

    如果两个集合顺序一致,则可以用改良版的 for 循环


    __EOF__

  • 本文作者: 女友在高考
  • 本文链接: https://www.cnblogs.com/javammc/p/16854029.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    [Ubuntu 20.04] HEIF图像格式与libheif库及其工具的使用
    【Linux】多线程
    人工神经网络案例分析题,人工神经网络算法实例
    开发实践丨昇腾CANN的推理应用开发体验
    Linux之输入输出重定向和管道
    生产者消费模式
    flutterdart chacha20加密
    高薪程序员&面试题精讲系列131之Eureka如何实现高可用?自我保护机制是怎么回事?
    传输层-用户数据报协议(UDP)
    Flex布局—固定定位+流式布局—主轴对齐—侧轴对齐—伸缩比
  • 原文地址:https://www.cnblogs.com/javammc/p/16854029.html