【性能优化】两种方案优化双层for循环语句 附代码和实例

现有如下需求,需在订单信息类中根据用户信息类的userId匹配到对应的信息并输出商品skuId。

先给出实体类:用户信息类UserInfo和订单信息类OrderInfo。用户信息类如下所示:

/**
 * @author Carson Chu
 * @date 2020/1/13 19:43
 * 用户信息类
 */
@Data
public class UserInfo {
    /* 用户Id */
    private String userId;

    /* 用户名 */
    private String name;

    /* 用户性别 */
    private String gender;

    public UserInfo(String userId, String name, String gender) {
        this.userId = userId;
        this.name = name;
        this.gender = gender;
    }
}

订单信息类如下:

/**
 * @author Carson Chu
 * @date 2020/1/13 19:44
 * 订单信息类
 */
@Data
public class OrderInfo {
    /* 用户Id */
    private String userId;

    /* 订单号 */
    private Long orderId;

    /* 商品sku */
    private Long skuId;

    /* 商品价格 */
    private BigDecimal price;

    public OrderInfo(String userId, Long orderId, Long skuId, BigDecimal price) {
        this.userId = userId;
        this.orderId = orderId;
        this.skuId = skuId;
        this.price = price;
    }
}

假设用户信息表和订单信息表中各有十万条数据,需要匹配到商品skuId,先采用双层for循环,附上main()方法逻辑:

/**
 * @author Carson Chu
 * @date 2020/1/13 20:09
 */
public class Main {
    public static void main(String[] args) {
        /* 用户信息集合 */
        List<UserInfo> userInfoList = Lists.newArrayList();
        /* 订单信息集合 */
        List<OrderInfo> orderInfoList = Lists.newArrayList();
        /* 手动向集合中添加十万条数据 */
         for (int i = 0; i < 100000; i++) {
            String userId = i + "";
            String name = "用户" + i;
            String gender = "female";
            UserInfo userInfo = new UserInfo(userId, name, gender);
            Long orderId = Long.valueOf(createNum(6));
            Long skuId = Long.valueOf(createNum(8));
            OrderInfo orderInfo = new OrderInfo(userId, orderId, skuId, new BigDecimal("66"));
            userInfoList.add(userInfo);
            orderInfoList.add(orderInfo);
        }       
         
        long start = System.currentTimeMillis();
        for (UserInfo userInfo : userInfoList) {
            for (OrderInfo orderInfo : orderInfoList) {
                if (userInfo.getUserId().equals(orderInfo.getUserId())) {
                    System.out.println(orderInfo.getSkuId());
                }
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("采用原始的双层for循环查询累计耗时:" + (end - start) + "毫秒");
    }
    
    /**
     * 生成指定长度的随机数
     *
     * @param length
     * @return
     */
    public static String createNum(int length) {
        StringBuilder sb = new StringBuilder();
        Random rand = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(rand.nextInt(10));
        }
        return sb.toString();
    }
}

采用双层for循环的运行时间很长,将近4分钟,如下图所示:
在这里插入图片描述
如上图所示,查询消耗十分巨大,这种响应时间,玩笑开大了!这还不包括复杂业务场景下的I/O消耗等,可以说性能极其糟糕,线上环境绝对不允许如此缓慢的响应时间。双层for循环时间复杂度O(N2),效率极低,并且性能随着处理容量N呈急剧下降(开发中应该保证性能平缓下降)甚至将导致接口直接崩溃。那么,应该如何优化呢?

方案一:HashMap

将main()方法中的主要处理逻辑修改为采用哈希,使用两次单层循环:

 		long start = System.currentTimeMillis();
        Map<String, Long> hashMap = new HashMap<>();
         /* 先将所有的订单信息都放在HashMap,哈希的查找时间复杂度是O(1) */
        for (OrderInfo orderInfo : orderInfoList) {
            hashMap.put(orderInfo.getUserId(), orderInfo.getSkuId());
        }
        for (UserInfo userInfo : userInfoList) {
            System.out.println(hashMap.get(userInfo.getUserId()));
        }
        long end = System.currentTimeMillis();
        System.out.println("利用HashMap查询累计耗时:" + (end - start) + "毫秒");

在这里插入图片描述
如上图所示,哈希的查询十分的快,响应时间缩小到了915毫秒,为秒级,效率比双层for循环提高了两百多倍。

方案二:JDK8的lambda表达式和stream流

此外也可以采用jdk8的lambda表达式:

long start = System.currentTimeMillis();
        // 将List转为Map,这里key一定要为唯一值
        Map<String, UserInfo> userInfoMap = userInfoList.stream().collect(Collectors.toMap(userInfo -> userInfo.getUserId(), userInfo -> userInfo));
        // 商品skuId集合
        List<Long> skuIdList = Lists.newArrayList();
        skuIdList = orderInfoList.stream().map(orderInfo -> {
            return getSkuId(userInfoMap.get(orderInfo.getUserId()), orderInfo);
        }).collect(Collectors.toList());
        skuIdList.stream().forEach(skuId -> {
            System.out.println("商品skuId:" + skuId);
        });
        long end = System.currentTimeMillis();
        System.out.println("利用lambda表达式和stream流查询累计耗时:" + (end - start) + "毫秒");

附加匹配用户信息和订单信息的方法逻辑:

/**
     * 匹配用户信息和订单信息,成功匹配后返回用户对应的商品skuId
     *
     * @param userInfo
     * @param orderInfo
     * @return
     */
    private static Long getSkuId(UserInfo userInfo, OrderInfo orderInfo) {
        if (orderInfo.getUserId().equals(userInfo.getUserId())) {
            return orderInfo.getSkuId();
        }
        return null;
    }

在这里插入图片描述
如上图所示,耗时也是毫秒级的,和哈希效率相当,因为事实上这种stream流就是基于HashMap的。双层for循环和这两种方案相比,执行效率和用户体验上可谓是天差地别。

发布了28 篇原创文章 · 获赞 12 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Carson_Chu/article/details/103961755