震惊!没想到你居然是这样的for循环(UC打钱!)

一.起因

由于最近业务的数据量和复杂度有点高,所有对效率这方便需要格外重视,不然代码是写完了,效率太低了,代码中使用到了非常多的循环,所以我折腾了几种循环的性能问题

二.对几种常见的做法进行分析

  1. 这里先附上测试代码
package com.xx.controller;

import com.xx.entity.Department;
import com.xx.entity.User;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author aqi
 * DateTime: 2020/8/19 9:42 上午
 * Description: No Description
 */
public class Demo {

    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();

        for (int i = 0; i < 1000000; i++) {
            User user = new User();
            user.setId(i);
            user.setName("name:" + i);
            String departmentId = i % 3 == 1 ? "1000" : (i % 3 == 2 ? "2000" : "3000");
            user.setDepartmentId(departmentId);
            userList.add(user);

        }

        List<Department> departmentList = new ArrayList<>();
        departmentList.add(new Department("1000", "部门1"));
        departmentList.add(new Department("2000", "部门2"));
        departmentList.add(new Department("3000", "部门3"));

        long start1 = System.currentTimeMillis();
        // 增强for
        // O(n * n)
        for (User user : userList) {
            for (Department department : departmentList) {
                if (Objects.equals(user.getDepartmentId(), department.getId())) {
                    user.setDepartmentName(department.getDepartmentName());
                    break;
                }
            }
        }
        long end1 = System.currentTimeMillis();
        System.out.println("增强for耗时:" + (end1 - start1));

        // 普通for
        // O(n * n)
        for (int i = 0; i < userList.size(); i++) {
            for (int j = 0; j < departmentList.size(); j++) {
                if (Objects.equals(userList.get(i).getDepartmentId(), departmentList.get(j).getId())) {
                    userList.get(i).setDepartmentName(departmentList.get(j).getDepartmentName());
                    break;
                }
            }
        }
        long end2 = System.currentTimeMillis();
        System.out.println("普通for耗时:" + (end2 - end1));

        // 第一种stream写法,这个时间复杂度是O(n*n)
        // O(n * n)
        userList.forEach(e -> departmentList.forEach(ex -> {
            if (Objects.equals(e.getDepartmentId(), ex.getId())) {
                e.setDepartmentName(ex.getDepartmentName());
            }
        }));
        long end3 = System.currentTimeMillis();
        System.out.println("第一种stream写法耗时:" + (end3 - end2));

        // 第二种stream写法O(n + n + 1) = O(n)
        // O(n)
        Map<String, Department> collect = departmentList.stream().collect(Collectors.toMap(Department::getId, Function.identity()));
        // O(n)
        userList.forEach(e -> {
            // O(1)
            Department department = collect.get(e.getDepartmentId());
            e.setDepartmentName(department.getDepartmentName());
        });

        long end4 = System.currentTimeMillis();
        System.out.println("第二种stream写法耗时:" + (end4 - end3));

    }

}

  1. 查看测试结果
  • 1w条数据

在这里插入图片描述

  • 10w条数据

在这里插入图片描述

  • 50w条数据

在这里插入图片描述

  • 100w条数据

在这里插入图片描述

  • 500w条数据

在这里插入图片描述

  • 1000w条数据

在这里插入图片描述

这里列个表格,方便分析(时间单位ms)

数量for循环增强for循环第一种写法第二种写法
1w799012
10w212410124
50w33418625
100w467610145
500w159403207116
1000w2991053324210
  1. 分析结果(这里使用的是ArrayList进行的测试,并没有对LinkedList进行测试,并且测试结果也可能没有那么的精确,这里采用10次求平均值的方式
  • 这里我们发现普通for的执行效率是比增强for效率要高的,并且随着数据量的增大,性能差距越明显
  • 随着数据量的不断增多,增强for的性能急剧下降,普通for循环的下滑并没有那么严重
  • 后面两种都是采用stream流处理的方式进行的,但是第一种写法在数据量不大的情况下,效率似乎有些太差了,不过随着数据量的增加,执行耗时并没有太大的变化,应该主要是创建流比较耗时
  • 第二种写法整体效率和普通for循环差不多,并且在数据量偏大的时候性能稍优于普通for循环

总结:普通for循环的执行效率和第二种写法的效率远高于其他两种,增强for循环性能稍弱,但效率随数据量变多效率下滑十分严重,第一种写法数据量不多时效率偏低,但是数据量较大是效率还算可以

三.分析原因

  1. 先从两种传统的for循环开始分析,因为都是二重循环,所以时间复杂度都是O(n * n),理论上时间应该差不了太多,但是我们只要看一下这两者的源码就可以知道为什么了

for循环和增强for循环获取数据源码对比图

  • 左边的是fori循环,可以看到只进行了一个数组下标是否越界的判断就,直接返回了这个数据

  • 右边的是增强for循环,可以看到在获取数据之前进行了许多的判断,再返回数据

  1. 第一种写法的时间复杂度是O(n * n),而第二种写法的时间复杂度是O(n + n) = O(n),可以看出第二种写法的时间复杂度是最小的,但是因为是流处理,所以要创建流,这就导致在数据量小的时候流处理并不占优势,甚至效率十分的低下,但是由于第二种写法将时间复杂度降到了O(n)这才大幅度减少了其耗时

这里附上折线图,便于观察
在这里插入图片描述

四.总结

  1. 有些人偏爱使用stream流的形式去遍历集合(比方说我自己),但是可以看到在数据量不大的情况下,效率是偏低的,但是虽然说偏低,但也是毫秒级别的,只是相较于传统的for循环效率偏低(stream.foreach好像没有办法跳出嵌套循环,不知道有没有谁知道可以怎么做,这样可以减少不必要的循环次数
  2. 并且在需要使用stream去进行嵌套循环的时候,推荐使用第二种写法,这种写法使用HashMap查找数据时间复杂度为O(1)的特点,降低了时间复杂度,并且不论在数据量多少的情况下,效率都十分的稳定,爱了爱了,这里我本人是比较推荐这种写法的
  3. 这里的增强for在大于100w数据的情况下性能开始极端下降,这个是需要十分注意的
  4. 传统for循环的性能也是十分稳定的,但是其使用的时候需要些很多的i,j,k类似的下标参数,这样的代码看起来会比较的混乱,代码写出来也会偏长,我不是很喜欢用,看各自喜好吧
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值