Java for循环嵌套for循环,你需要懂的代码性能优化技巧

前言

本篇分析的技巧点其实是比较常见的,但是最近的几次的代码评审还是发现有不少兄弟没注意到。

所以还是想拿出来说下。
 

正文

是个什么场景呢? 

就是 for循环 里面还有 for循环, 然后做一些数据匹配、处理 这种场景。

 

我们结合实例代码来看看。

场景示例:

比如我们现在拿到两个list 数据 ,

一个是 User List 集合 ;

另一个是 UserMemo List集合;


我们需要遍历 User List ,然后根据 userId 从 UserMemo List 里面取出 对应这个userId 的 content 值,做数据处理。

 

代码  User.java :

import lombok.Data;

@Data
public class User {
    private Long userId;
    private String name;
}

代码 UserMemo.java :

import lombok.Data;

@Data
public class UserMemo {
    private Long userId;
    private String content;
}

模拟 数据集合 :

5W 条 user 数据 , 3W条 userMemo数据 

    public static List<User> getUserTestList() {
        List<User> users = new ArrayList<>();
        for (int i = 1; i <= 50000; i++) {
            User user = new User();
            user.setName(UUID.randomUUID().toString());
            user.setUserId((long) i);
            users.add(user);
        }
        return users;
    }

    public static List<UserMemo> getUserMemoTestList() {
        List<UserMemo> userMemos = new ArrayList<>();
        for (int i = 30000; i >= 1; i--) {
            UserMemo userMemo = new UserMemo();
            userMemo.setContent(UUID.randomUUID().toString());
            userMemo.setUserId((long) i);
            userMemos.add(userMemo);
        }
        return userMemos;
    }

先看平时大家不注意的时候可能会这样去写代码处理 :

 ps: 其实数据量小的话,其实没多大性能差别,不过我们还是需要知道一些技巧点。

代码:

    public static void main(String[] args) {
        List<User> userTestList = getUserTestList();
        List<UserMemo> userMemoTestList = getUserMemoTestList();


        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        for (User user : userTestList) {
            Long userId = user.getUserId();
            for (UserMemo userMemo : userMemoTestList) {
                if (userId.equals(userMemo.getUserId())) {
                    String content = userMemo.getContent();
                    System.out.println("模拟数据content 业务处理......"+content);
                }
            }
        }


        stopWatch.stop();
        System.out.println("最终耗时"+stopWatch.getTotalTimeMillis());


    }

我们来看看 这时候的一个耗时情况 :

相当于迭代了 5W * 3W 次 

可以看到用时 是 26857毫秒 

 

其实到这,插入个题外点,如果说每个userId 在 UserMemo List 里面 都是只有一条数据的场景。

        for (User user : userTestList) {
            Long userId = user.getUserId();
            for (UserMemo userMemo : userMemoTestList) {
                if (userId.equals(userMemo.getUserId())) {
                    String content = userMemo.getContent();
                    System.out.println("模拟数据content 业务处理......"+content);
               
                }
            }
        }
        

单从这段代码有没有问题 ,有没有优化点。

显然是有的, 因为当我们从内循环UserMemo List里面找到匹配数据的时候, 没有做其他操作了。

这样 内for循环会继续下,直到跑完再进行下一轮整体循环。

所以,仅针对这种情形,1对1的或者说我们只需要找到一个匹配项,处理完后我们 应该使用 break


我们来看看 加上 break 的一个耗时情况 :

 代码:

    public static void main(String[] args) {
        List<User> userTestList = getUserTestList();
        List<UserMemo> userMemoTestList = getUserMemoTestList();


        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        for (User user : userTestList) {
            Long userId = user.getUserId();
            for (UserMemo userMemo : userMemoTestList) {
                if (userId.equals(userMemo.getUserId())) {
                    String content = userMemo.getContent();
                    System.out.println("模拟数据content 业务处理......"+content);
                    break;
                }
            }
        }


        stopWatch.stop();
        System.out.println("最终耗时"+stopWatch.getTotalTimeMillis());


    }

耗时情况:
 

可以看到 从 2W 多毫秒 变成了 1W 多毫秒, 这个break 加的很OK。

 


回到我们刚才, 平时需要for 循环 里面再 for 循环 这种方式,可以看到耗时是 2万6千多毫秒。

那如果场景更复杂一定, 是for 循环里面 for循环 多个或者, for循环里面还有一层for 循环 ,那这样代码耗时真的非常恐怖。


那么接下来这个技巧点是 使用map 去优化 :

 

代码:
 

    public static void main(String[] args) {
        List<User> userTestList = getUserTestList();
        List<UserMemo> userMemoTestList = getUserMemoTestList();


        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Map<Long, String> contentMap =
                userMemoTestList.stream().collect(Collectors.toMap(UserMemo::getUserId, UserMemo::getContent));

        for (User user : userTestList) {
            Long userId = user.getUserId();
            String content = contentMap.get(userId);

            if (StringUtils.hasLength(content)) {
                System.out.println("模拟数据content 业务处理......" + content);
            }

        }

        stopWatch.stop();
        System.out.println("最终耗时" + stopWatch.getTotalTimeMillis());


    }

看看耗时:

 

为什么 这么显著的效果 ?


这其实就是时间复杂度,

for循环嵌套for循环,


就好比 循环每一个 user ,拿出 userId 

需要在里面的循环从 userMemo list集合里面 按顺序去开盲盒匹配,


拿出第一个,看看userId ,拿出第二个,看看userId ,一直找匹配的。

而我们提前对 userMemo list集合 做一次 遍历,转存储在map里面 。


map的取值效率 在多数的情况下是能维持接近 O(1) 的 , 毕竟数据结构摆着,数组加链表。


相当于拿到userId  想去开盲盒的时候, 根据userId 这个key  hash完能直接找到数组里面的索引标记位, 如果底下没链表(有的话O(logN)),直接取出来就完事了。

然后补充一个getNode的代码注释 : 


    /**
     * Implements Map.get and related methods.
     * 这是个 Map.get 的实现 方法
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
//    final 写死了 无法更改 返回 Node 传入查找的 hash 值 和 key键
    final Node<K,V> getNode(int hash, Object key) {
//        tab 还是 哈希表
//        first 哈希表找的链表红黑树对应的 头结点
//        e 代表当前节点
//        k 代表当前的 key
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//        赋值 并过滤 哈希表 空的长度不够的 对应位置没存数据的 都直接 return null
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
//            头结点就 找到了 hash相等值相等 或者 不空的 key 和当前节点 equals
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
//            头结点不匹配 没找到就 就用 next 找
            if ((e = first.next) != null) {
//                是不是红黑树 的
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//                红黑树就直接 调用 红黑树内查找

//                不为空或者没找到就do while 循环
                do {
//                    当前节点 找到了 hash相等值相等 或者 不空的 key 和当前节点 equals
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
=

 

按照目前以JDK8 的hash算法,起hash冲突的情况是非常非常少见了。
最恶劣的情况,只有当 全部key 都冲突, 全都分配到一个桶里面去都占用一个位置 ,这时候就是O(n),这种情景不需要去考虑。

好了,该篇就到这。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/626.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot+WebSocket实时监控异常

# 写在前面此异常非彼异常&#xff0c;标题所说的异常是业务上的异常。最近做了一个需求&#xff0c;消防的设备巡检&#xff0c;如果巡检发现异常&#xff0c;通过手机端提交&#xff0c;后台的实时监控页面实时获取到该设备的信息及位置&#xff0c;然后安排员工去处理。因为…

Java实现调用第三方相关接口(附详细思路)

目录1.0.简单版2.0.升级版2-1.call.timeout()怎么传入新的超时值2-2.timeout(10, TimeUnit.SECONDS)两个参数的意思&#xff0c;具体含义3.0.进阶版3-1.java.net.SocketTimeoutException: 超时如何解决4.0.终极版1.0.简单版 以下是一个使用 Java 实际请求“第三方”的简单示例代…

一眼看破五花八门的链表结构

文章目录&#x1f4d5;一&#xff1a;五花八门的链表结构&#x1f4d6;链表与数组的简单对比&#x1f4d6;单链表&#x1f4d6;循环链表&#x1f4d6;双向链表&#x1f4d5;二&#xff1a;链表VS数组性能大比拼&#x1f47f;最后说一句&#x1f431;‍&#x1f409;作者简介&am…

数据挖掘(2.1)--数据预处理

一、基础知识 1.数据的基本概念 1.1基础知识 数据是数据对象(Data Objects)及其属性(Attributes)的集合。 数据对象(一条记录、一个实体、一个案例、一个样本等)是对一个事物或者物理对象的描述。 数据对象的属性则是这个对象的性质或特征&#xff0c;例如一个人的肤色、眼球…

GPT-4 性能炸天:10 秒做出一个网站,在考试中击败 90% 人类

一、GPT-4&#xff0c;吊打ChatGPT&#xff01; 一觉醒来&#xff0c;万众期待的 GPT-4&#xff0c;它来了&#xff01; OpenAI老板Sam Altman直接开门见山地介绍道&#xff1a;这是我们迄今为止功能最强大的模型&#xff01; 二、GPT-4&#xff0c;新功能一览 究竟有多强&am…

Python人脸识别

#头文件&#xff1a;import cv2 as cvimport numpy as npimport osfrom PIL import Imageimport xlsxwriterimport psutilimport time#人脸录入def get_image_name(name):name_map {f.split(.)[1]:int(f.split(.)[0]) for f in os.listdir("./picture")}if not name…

Java的jar包打包成exe应用

将springboot项目使用maven打出的jar包&#xff0c;打成windows平台下exe应用程序包&#xff08;自带jre环境&#xff09;。 工具&#xff1a;1、exe4j 2、Inno Setup 工具放到网盘&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1ZHX8P7u-7GBxaC6uaIC8Ag 提取码&#x…

SpringBoot-核心技术篇

技术掌握导图 六个大标题↓ 配置文件web开发数据访问单元测试指标指控原理解析 配置文件 1.文件类型 1.1、properties 同以前的properties用法 1.2、yaml 1.2.1、简介 YAML是 “YAML Aint Markup Language”&#xff08;YAML不是一种标记语言&#xff09;的递归缩写。在…

76.qt qml-QianWindow开源炫酷界面框架(支持白色暗黑渐变自定义控件均以适配)

界面介绍界面支持: 透明 白色 黑色 渐变 单色 静态图 动态图侧边栏支持:抽屉、带折叠、多模式场景控件已集成: 暗黑风格 高亮风格、并附带个人自定义控件及开源demo白色场景如下所示:单色暗黑风格如下所示:用户自定义皮肤如下所示:皮肤预览如下所示:b站入口:https://www.bilibi…

2023年跨境电商行业研究报告

第一章 行业发展 1.1 概况 跨境电商&#xff08;Cross-border e-commerce&#xff09;是指通过互联网销售商品或服务&#xff0c;跨越国家或地区边界&#xff0c;实现国际贸易的一种商业模式。跨境电商的兴起得益于全球化和数字化的趋势&#xff0c;以及互联网的普及和支付、…

Linux常用命令——基于Ubuntu22.04

本文介绍了一些Linux的常用命令。为了便于快速检索命令位置&#xff0c;文章二级标题都以“命令&#xff1a;命令的作用”展示&#xff0c;有些命令会先介绍命令的几个常用参数&#xff0c;然后结合具体的操作展示命令的使用。为了便于记忆&#xff0c;也会提到命令是由哪些短语…

【链表OJ题(五)】合并两个有序链表

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录链表OJ题(五)1. 合并…

elasticsearch全解 (待续)

目录elasticsearchELK技术栈Lucene与Elasticsearch关系为什么不是其他搜索技术&#xff1f;Elasticsearch核心概念Cluster&#xff1a;集群Node&#xff1a;节点Shard&#xff1a;分片Replia&#xff1a;副本全文检索倒排索引正向和倒排es的一些概念文档和字段索引和映射mysql与…

原来CSS 也可以节流啊

Ⅰ、前言 「节流」 是为了减少请求的触发频率&#xff0c;不让用户点的太快&#xff0c;达到节省资源的目的 &#xff1b;通常 我们采用 JS 的 定时器 setTimeout &#xff0c;来控制点击多少秒才能在触发&#xff1b;其实 通过 CSS 也能达到 「节流」 的目的&#xff0c;下面…

面试官:MQ的好处到底有哪些?

&#x1f497;推荐阅读文章&#x1f497; &#x1f338;JavaSE系列&#x1f338;&#x1f449;1️⃣《JavaSE系列教程》&#x1f33a;MySQL系列&#x1f33a;&#x1f449;2️⃣《MySQL系列教程》&#x1f340;JavaWeb系列&#x1f340;&#x1f449;3️⃣《JavaWeb系列教程》…

大数据核心技术是什么

大数据的核心层&#xff1a;数据采集层、数据存储与分析层、数据共享层、数据应用层&#xff0c;可能叫法有所不同本质上的角色都大同小异。 大数据的核心技术都包括什么&#xff1f; 1、数据采集 数据采集的任务就是把数据从各种数据源中采集和存储到数据存储上&#xff0c…

如何用python代码,更改照片尺寸,以及更换照片底色

前言 python浅浅替代ps&#xff1f;如何用代码来p证件照并且更换底色&#xff1f; 唉&#xff0c;有个小姐姐给我扔了张照片&#xff0c;叫我帮忙给她搞成证件照的尺寸还得换底色&#xff0c;她说自己忙的很 可惜电脑上没有ps只有pycharm&#xff0c;没得办法只能来试试看代…

Printk打印内核日志

一、背景 Linux 内核中提供了内核日志打印的工具printk。它的使用方式C语言中的printf是类似的。接下来我们介绍一下printk的使用方式。本文以打印Binder中的日志为例&#xff0c;进行演示。 printk的方法声明和日志级别binder驱动中增加 打印代码android系统中查看日志信息 …

第四季新星计划即将开启,博客之星取消拉票你怎么看?

catalogue&#x1f31f; 写在前面&#x1f31f; 线下创机遇&#x1f31f; 新星计划&#x1f31f; 做导师可以得到什么&#x1f31f; 新星计划跟原力计划有何不同&#xff1f;&#x1f31f; 博客之星新玩法你怎么看&#xff1f;&#x1f31f; 写在前面 哈喽&#xff0c;大家好&…

为什么程序员喜欢这些键盘?

文章目录程序员的爱介绍个人体验程序员的爱 程序员是长时间使用计算机的群体&#xff0c;他们需要一款高品质的键盘来保证舒适的打字体验和提高工作效率。在键盘市场上&#xff0c;有很多不同类型的键盘&#xff0c;但是对于程序员来说&#xff0c;机械键盘是他们最钟爱的选择…
最新文章