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 getUserTestList() { List 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 getUserMemoTestList() { List 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 userTestList = getUserTestList(); List 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 userTestList = getUserTestList(); List 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 userTestList = getUserTestList(); List userMemoTestList = getUserMemoTestList(); StopWatch stopWatch = new StopWatch(); stopWatch.start(); Map 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 getNode(int hash, Object key) { // tab 还是 哈希表 // first 哈希表找的链表红黑树对应的 头结点 // e 代表当前节点 // k 代表当前的 key Node[] tab; Node 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)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),这种情景不需要去考虑。
原文链接;https://mp.weixin.qq.com/s/Yo_k5B9j6nbK1_o3fn-6TA
亲子六则五知识的力量(上)文佑我浮生马克思曾经这样说过任何时候,我也不会满足,越是多读书,就越是深刻地感到不满足,越感到自己知识贫乏。科学是奥妙无穷的。奇奇很喜欢看书,从很小的时候起就对文字有很高的求知欲,
新生儿听力筛查没通过?先别慌,弄懂这几个问题,你就知道怎么办我家宝宝出生后做听力筛查没通过,孩子听力是不是有问题啊,我们该怎么办?前天后台收到了这样一个提问,提问的是一位新手宝爸。他说因为宝宝听力的问题,他和宝妈吃不下睡不着,尤其是宝妈,一
追星青年,记录中国空间站成长9月21日凌晨,刘博洋展示当日拍摄的中国空间站过境画面。2022年,90后天体物理学博士刘博洋,用自主研发的光学跟踪程序在地面成功拍摄到中国空间站清晰特写影像,用镜头记录着中国空间
中国移动发力算力建设产业链合作企业破千家12月12日,在中国移动2022全球合作伙伴大会期间,6G元宇宙人工智能物联网算力等前沿技术被多次提及。中国移动副总经理李慧镝表示,中国移动将不断丰富算力供给应用和服务方式,推进算
心脏再强也要避免这8件事每年冬季都是心脑血管疾病的发病高峰,在严酷的寒潮下,学会如何保护心脏尤为重要。而日常生活中,很多小事看起来微不足道却是损害心脏健康的隐患,您可要重视起来。下面给您总结了8件需要注意
一个超猛的止痛方补气活血,拿下腰疼腿疼肩背疼止全身各种疼痛大家好,我是沈医生,今天给大家介绍一个止痛方,出自医学泰斗之手,可以补足一身元气,化散一身瘀血,止腰疼,腿疼,全身各种疼痛!现在很多人,都经常会出现腰疼,腿疼,脖子疼,或者是胳膊,
放开后自述变阳痊愈全过程记录记录过程,全程3天左右。我想提醒大家冬天天气冷很容易中招流感。希望对大家有参考。不必惊慌,多喝热水很关键。很多人会出现假性头晕头疼测体温发展不发烧,大部分是心里作用或者衣服穿少了,
两味药拿下便秘!一味管上面,一个管下面,从头通到尾,建议收藏今天这篇文章,我想和大家聊聊恼人的老便秘。看官请辨别仔细我说的是老便秘。偶尔的便秘,通过饮食调养,改善生活节奏,可以自然缓解。我们这里说的,乃是因为种种原因,便秘常年发生,经久不愈
买不到退烧药,西瓜和梨可以替代一下,可能效果更好以下言论仅供参考,如有后果,自负。买不到退烧药,西瓜和梨,可以替代一下。因为这两种水果,在中药层面,是清热的。同是,绿豆也性寒,也可以退烧。还有薏米,也可以退烧。另外,从冰箱里拿出
得了高血压究竟对身体有哪些影响,为您盘点高血压的13大危害原发性高血压病是病因尚未明确而以血压升高为主要临床表现伴有和不伴有多种危险因素的的综合症,常影响心脑肾血管眼底的结构和功能的改变和损害,引起相关疾病的发生,最终导致这些器官功能衰竭
四连胜!上海男篮大胜宁波队,王哲林和新外援布莱德索合力砍下56分上海大鲨鱼男篮在今天下午进行的本赛季CBA常规赛第12轮的一场比赛中,以12588轻松战胜了排名垫底的宁波男篮。最近取得四连胜的他们,在经历了赛季初低迷之后,终于迎来了一波强势反弹
同样演中年窝囊男,把唐曾和郭京飞放一起来看,差别就出来了在幸福到万家上线之初,最让观众讨厌的角色莫过于有预谋婚闹的万传家,但随着剧情的发展,更让观众讨厌的变成了王庆来。在前期剧情中,王庆来这一角色是让观众既觉得可怜又觉得可气的,可怜之处
餐饮业全力以复餐饮业一头是百姓的餐桌,一头关系着经济发展。数据显示,2021年我国餐饮市场规模达4。7万亿,餐饮行业的直接从业人员在2000万人左右。目前,各部门已出台多项政策措施,全面助力餐饮
他曾被村民劝说去要饭,26岁逆袭成阿里工程师天下网商丁洁编辑李丹超我叫郭百岭,谐音过百岭。7月4日,湖北工业大学科技楼,坐着轮椅上台的郭百岭,作为优秀毕业生代表回母校演讲。他的另一个身份,是阿里巴巴CRO商业决策部的一名数据
警虎傅政华被起诉01司法部原部长傅政华被提起公诉据最高检官网消息,全国政协社会和法制委员会原副主任傅政华涉嫌受贿徇私枉法案,由国家监察委员会调查终结,经最高人民检察院指定,由吉林省长春市人民检察院
英媒新型分子计算机能耗超低据英国新科学家周刊网站7月1日报道,利用分子解决问题的计算机的能耗是传统计算机的万分之一。如果做得更大,这些生物计算机能有效解决通常需要花大量时间和精力来应对的复杂物流问题。在计算
7月11日星期一复盘7月11复盘。打败市场的不只是莹莹的一句话先说观点再说结论。中期继续看多,震荡上行。这个周末市场消息复杂,防疫板块与复苏主线的跷跷板效应再现,周期性板块出现回落态势。这导致沪指直接
2023年高考作文命题核心主题预测2023年高考作文命题核心主题预测考情预测1。主流价值与主流意识得到进一步强化,凸显了社会主义核心价值观的引领,进一步落实了立德树人的根本任务,更加明确了高考评选拔德才兼备全面发展
敲黑板!四个维度带你快速解读反有组织犯罪法四个维度快速解读反有组织犯罪法今年5月1日,反有组织犯罪法正式施行,标志反有组织犯罪工作进入全面依法开展的新阶段,该法律有哪些精华内容?四个维度带你快速解读一hr明确依法防治有组织
周希汉的2个师,击溃桂军9个师,陈赓却急电快撤,不留一兵一卒1949年5月22日下午,赣江西岸,2野4兵团13军军长周希汉激动地通过报话机向兵团总部汇报请马上向首长报告,敌正在溃退,我们正在乘胜追击!步话机另一头传来了一阵欢呼声。因为4兵团
两亩白皮松傍晚,太阳站在西山顶上,敛去了光芒,带着一天彤云,低着红红的笑脸,俯察着苍生与万物。在床上窝了一下午,吃完晚饭,借着凉爽去看看自家地里那可有可无的两亩白皮松。两个月前打了一次除草剂
夏雨庭外定档将播,3大看点,10位实力派坐镇,该火了吧?到目前为止,今年的悬疑剧,你追了哪部?在我看来,得分为两部分来看如果仅就剧集质量和可看性而言,我推荐檀健次和金世佳主演的猎罪图鉴喜欢演员飙戏的话,我推荐重生之门,当然剧中年轻的主演