空间换时间-五秒出解:从900ms到5ms的幕后优化大揭秘!

news/2024/5/8 1:42:19/文章来源:https://blog.csdn.net/maniuT/article/details/132454463

作者:麦客奥德彪

探索数据操作的效率是软件开发中的一项重要任务。开发中遇到了Java中的ArrayListremoveAll方法,意外发现当面对大量数据时,其执行效率可能会让人瞠目结舌,高达900毫秒以上!然而,通过一系列分析和优化实验,我成功将执行时间从900ms优化到令人惊叹的5ms以内。

平时各种数据结构,各种算法优化都在储备,但是实际开发时一般情况下真的不会使用,就比如今天的这个场景:

RV 中按照折叠方式展示数据,但是数据量较大,且数据关系层级不明显,所以考虑使用RV多布局实现,然后在折叠层中进行打开关闭操作,由于折叠时的数据巨大,如果进行RV item中的折叠动作的话会发生渲染问题。所以直接简单粗暴从目标集合中删除需要折叠的 然后打开时再进行添加,在这个想法之下,引出了这个问题:

一、removeAll 导致UI卡顿

查看发现卡顿的来源就是removeAll 时的耗时,从目标集合中移除3000个元素,耗时差不多900ms.

一开始同事和同事讨论时以为是RV的渲染导致的卡顿,随即有同事指出RV的显示机制在多布局的模式下其实不会存在渲染卡顿的(这就是人多力量大的典范哈),然后测试发现是移除数据时,耗时导致的。

具体步骤大概是这样:

// 1. 测试数据,由于测试数据比较简单,耗时会比业务中的实际数据低很多
ArrayList<Demo> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {list.add(new Demo("index=", i));
}
long start = System.currentTimeMillis();
List<Demo> demos = new ArrayList<>(list.subList(1000, 4000));
list.removeAll(demos);
System.out.println("用时 " + (System.currentTimeMillis() - start));

上述代码结果:

这样看来并不是什么耗时的操作,但是结合上面的使用场景,是在更新RV时进行的数据处理,那157ms 足以导致掉帧卡顿,毕竟我们有16.6ms 的刷新机制限制,在业务中数据较大时,我们测试直接呈现出来了900多ms

二、分析一下removeAll 为什么会有这么多的耗时?

在此处,我们要分析removeAll 的时间复杂度,第一步先看下方法的签名

boolean removeAll(Collection<?> c)

通过这个方法签名我们大致可以推断出影响removeAll 方法的时间复杂度的因素主要有两个:

  1. 要移除的元素数量
  2. 底层数据结构的实现

底层数据结构是基于数组实现的动态数组

假设要移除的元素数量为n

如果要移除的集合 c 是空集合,那么无需执行任何移除操作,直接返回,时间复杂度为 O(1)。

(1) 遍历A判断B.contains(A[i])若不包含,则 A[w] = A[i]  w从0递增(2) 遍历完成则将 A[w]往后部分置为 null(3) 看下上面(1)的B.contains(A[i])的逻辑:遍历B,一个个匹配,匹配到则返回B的index

简单来讲,两层循环时间复杂度就是O(n*m),如果ArrayList的大小为m,参数c的大小为c,且c < m,则在最坏情况下(即c中的所有元素都存在于ArrayList中),总的时间复杂度为O(m^2)

这就是平方级的时间复杂度。看来要解决此问题,就需要寻找其他方案

三、使用linkeList

对于删除操作,链表肯定是具备先天优势的,但是使用后发现, 时间有提升,但是效果不大,查看代码发现:

public boolean removeAll(Collection<?> c) {Objects.requireNonNull(c);boolean modified = false;Iterator<?> it = iterator();while (it.hasNext()) {if (c.contains(it.next())) {it.remove();modified = true;}}return modified;
}

它是循环使用next 取,完后remove 的,那效果自然也不行,但是如果我们自定义链表,直接修改删除区间集合的指针,倒是一个完美的解决方案,但是自定义数据结果,在业务中使用风险太大。

四、利用hashSet 解决

上面的耗时基本是来自两个方面

  1. 遍历
  2. 遍历 + 移动元素

那我们可以优先从遍历入手,大家都知道Hash 的查询时间是O(1), 这样的话用hash和LinkedList 就能更进一步优化耗时了

看下代码:

public static <T> List<T> removeAll(List<T> source, List<T> target) {LinkedList<T> result = new LinkedList<>();HashSet<T> hashSet = new HashSet<>(source);for (T t : source) {if (!hashSet.contains(t)) {result.add(t);}}return result;
}

source列表中移除与target列表中相同的元素,并将移除后的结果存储在一个新的列表中返回,以达到我们想要的效果,

在当时的业务中也是优化到了100ms 以内。

但是这个方式还存在一个弊端,当source非常大,target 又比较小时、或者都非常大时,还是会存在耗时。

五、利用空间换时间

我们在学习算法的时候,大家都听过一句话,算法优化基本就是时间换空间或者空间换时间,当我们需要确定一个参数优化到极致时,我们就可以在另一个方向上做优化。

我们验证一下:

思路是这样的,我们直接反取目标数据,

比如,从10万条数据中删除第1000到3000 的数据,那我们可以反取数据,取出0到999生成一个新集合,再取3001到10万为一个集合,最后再将二者的数据合并,加到目标集合中。

public static void main(String[] args) {ArrayList<Demo> list = new ArrayList<>();for (int i = 0; i < 100000; i++) {list.add(new Demo("index=", i));}long start = System.currentTimeMillis();// 创建集合ArrayList<Demo> demos1 = new ArrayList<>(list.subList(0, 999));ArrayList<Demo> demos2 = new ArrayList<>(list.subList(1000, list.size() - 1));
//        removeAll(list, demos);list.clear();list.addAll(demos1);list.addAll(demos2);System.out.println("用时 " + (System.currentTimeMillis() - start));}

在我们业务中,从900ms 优化到5ms, 效果是非常不错的,并且复杂数据测试的规模是10万中删除3000,基本满足大型列表删除场景了。

六、总结

为什么要写这个记录,都是一个非常简单的场景及使用方式,但是从发现这个问题到思考怎么解决却是一次算法学习的实际应用。我们在开发中,不会经常使用算法,但是像这种问题,我们可以用算法的角度去分析优化,这大概就是算法学习的意义

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能监控框架》:https://qr18.cn/FVlo89

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

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

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

相关文章

Linux系统安全:NAT(SNAT、DNAT)

目录 一.NAT 二.SNAT 三.DNAT 一.NAT NAT: network address translation&#xff0c;支持PREROUTING&#xff0c;INPUT&#xff0c;OUTPUT&#xff0c;POSTROUTING四个链 请求报文&#xff1a;修改源/目标IP&#xff0c; 响应报文&#xff1a;修改源/目标IP&#xff0c;根据…

期权分仓开户资金是否安全?具体保障措施有哪些?

网上关于期权分仓系统的真假一直都没有定论&#xff0c;两方人的争论也让很多没有接触过期权分仓系统的人摸不着头脑&#xff0c;那么期权分仓靠谱吗&#xff1f;资金在里面安全吗&#xff1f;下文为大家科普期权分仓开户资金是否安全?具体保障措施有哪些&#xff1f; 一、期权…

如何使用CSS实现一个拖拽排序效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 实现拖拽排序效果的CSS和JavaScript示例⭐ HTML 结构⭐ CSS 样式 (styles.css)⭐ JavaScript 代码 (script.js)⭐ 实现说明⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦…

震惊!友达台中厂长传过劳逝世 | 百能云芯

8月23日消息&#xff0c;近日面板大厂友达风波不断&#xff0c;8月3日有消息称&#xff0c;生产笔电的5代厂与电视的6代厂已经半年没有订单了&#xff0c;面板产业很惨&#xff0c;预计裁员100至200人。今天接到消息称&#xff0c;任职才1年的台中友达6A厂厂长&#xff0c;传因…

深度刨析数据要素,整合数据资源

数字经济已成为经济发展的一个核心引擎。数据作为新型生产要素&#xff0c;对传统生产方式变革具有重大影响&#xff0c;要构建以数据为关键要素的数字经济。 数据要素的定义 数据要素是指参与到社会生产经营活动中&#xff0c;为所有者或使用者带来经济效益的数据资源。因此…

国密算法介绍

一、简述 商用密码 商用密码是中华人民共和国政府用于非国家机密信息保护所采用的一系列密码技术和密码产品的总称&#xff0c;其相关技术为国家秘密。商用密码的研发及使用由国家密码管理局统一管理。 国密算法 国密算法是指中国自主设计和使用的密码算法标准&#xff0c;其…

MYSQL 统计停车时长百分比

SELECTCOUNT(*) AS 数量,subquery.total_count AS 总数,COUNT(*) * 100 / subquery.total_count AS 百分比,CASEWHEN park_long < 900 THEN 15分钟以内WHEN park_long > 900 AND park_long < 3600 THEN 15-60分钟WHEN park_long > 3600 AND park_long < 10800 T…

C语言刷题(14)

第一题 第二题 第三题 第四题 第五题 第六题 第七题

【Java】树结构数据的搜索

这里写自定义目录标题 需要实现的效果前端需要的json格式&#xff1a;一定是一个完整的树结构错误错误的返回格式错误的返回格式实现的效果 正确正确的返回格式正确的展示画面 后端逻辑分析代码总览 数据库表结构 需要实现的效果 前端需要的json格式&#xff1a;一定是一个完整…

springboot之多数据源配置

文章目录 一、多数据源的典型使用场景1 业务复杂&#xff08;数据量大&#xff09;2 读写分离 二、如何实现多数据源通过AbstractRoutingDataSource动态指定数据源多数据源切换方式AOPMyBatis插件 三、spring集成多个Mybatis框架 实现多数据源控制四、多数据源事务控制1.只使用…

DNQ算法原理(Deep Q Network)

1.强化学习概念 学习系统没有像很多其它形式的机器学习方法一样被告知应该做出什么行为 必须在尝试了之后才能发现哪些行为会导致奖励的最大化 当前的行为可能不仅仅会影响即时奖励&#xff0c;还会影响下一步的奖励以及后续的所有奖励 每一个动作(action)都能影响代理将来的…

智能安全帽_防抖视频定位智能安全帽头盔

智能安全帽具备出色的性能、超低功耗、广范围覆盖和简单的外围电路等优势&#xff0c;同时还拥有丰富的外部接口。它支持移动/联通/电信的4G5G网络&#xff0c;涵盖了LTE-TDD频段(B34/B38/B39/B40/B41)、LTE-FDD频段(B1/B3/B5/B8)、WCDMA频段(B1/B5/B8)、TD-SCDMA频段(B34/B39)…

EureKa快速入门

EureKa快速入门 远程调用的问题 多个服务有多个端口&#xff0c;这样的话服务有多个&#xff0c;硬编码不太适合 eureKa的作用 将service的所有服务的端口全部记录下来 想要的话 直接从注册中心查询对于所有服务 每隔一段时间需要想eureKa发送请求 保证服务还存活 动手实践 …

git 统计(命令)

查询某人某个时刻提交了多少代码 added 添加代码 removed 删除代码 total 总代码 git log --author刘俊秦 --since2023-08-01 00:00:00 --until2023-08-23 23:00:00 --prettytformat: --numstat | awk { add $1; subs $2; loc $1 - $2 } END { printf "added lines: %s…

【vue+uniapp】切换本页面(点击导航按钮)就刷新接口

查阅资料&#xff1a;uni-app官网 点击导航中图标&#xff0c;就执行的方法&#xff08;和methods同级&#xff09;&#xff1a; onTabItemTap(e) {this.getTaskTotal(); },

亚马逊运营卖家注意了!亚马逊Review政策大变动

Review对于电商卖家们而言都是十分重要的&#xff0c;这是客户是否选择下单的必要核心因素。几乎所有卖家对于Review政策的变动都会十分敏感。一旦亚马逊对Review有所行动&#xff0c;卖家们内心都会有所震动。这不是昨天一大早起来&#xff0c;大家就发现前台搜索页面显示Revi…

Java版B/S架构 智慧工地源码,PC、移动、数据可视化智慧大屏端源码

智慧工地是什么&#xff1f;智慧工地主要围绕绿色施工、安全管控、劳务管理、智能管理、集成总控等方面&#xff0c;帮助工地解决运营、管理方面各个难点痛点。在互联网的加持下促进项目现场管理的创新与发展&#xff0c;实现工程管理人员与工程施工现场的整合&#xff0c;构建…

Qt+C++动力监控动画仿真SCADA上位机

程序示例精选 QtC动力监控动画仿真SCADA上位机 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC动力监控动画仿真SCADA上位机>>编写代码&#xff0c;代码整洁&#xff0c;规则…

文件夹的批量下载

1.业务背景 公司想实现文件系统下载&#xff0c;上次图简单就草率的写了文件下载&#xff0c;这不趁着同事请假赶集吧这坑给填上。 2.遇到问题 刚准备开始写&#xff0c;就头疼&#xff0c;文件只要获得数据输出流就行&#xff0c;但是这文件夹需要维护层级关系。 前端…

(2023)Linux安装pytorch并使用pycharm远程编译运行

&#xff08;2023&#xff09;Linux安装pytorch并使用pycharm远程编译运行 安装miniconda 这部分参考我这篇博客的前半部分Linux服务器上通过miniconda安装R&#xff08;2022&#xff09;_miniconda 安装r_Dream of Grass的博客-CSDN博客 创建环境 创建一个叫pytorch的环境…