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

news2025/1/11 20:02:44

作者:麦客奥德彪

探索数据操作的效率是软件开发中的一项重要任务。开发中遇到了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.coloradmin.cn/o/917791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

景区气象站

景区气象站是通过各种传感器实现气象数据采集&#xff0c;这些传感器就是它的核心部件&#xff0c;拥有抗风、防腐、耐尘的优势&#xff0c;可以在各种复杂气象条件下稳定工作&#xff0c;为工作人员提供高精度的气象数据。搭配气象通讯主机&#xff0c;对传感器采集的数据进行…

2023-08-23 LeetCode每日一题(统计点对的数目)

2023-08-23每日一题 一、题目编号 1782. 统计点对的数目二、题目链接 点击跳转到题目位置 三、题目描述 给你一个无向图&#xff0c;无向图由整数 n &#xff0c;表示图中节点的数目&#xff0c;和 edges 组成&#xff0c;其中 edges[i] [ui, vi] 表示 ui 和 vi 之间有一…

echarts 之 科技感进度条

1.图片展示 2.代码实现 /* ng qty 进度条 */ <template><div class"ngqty-progress"><div class"ngqty-info"><span>X4</span><span>50%</span></div><div :id"barNgQtyProgress index" c…

ubuntu18.04复现yolo v8之CUDA与pytorch版本问题以及多CUDA版本安装及切换

最近在复现yolo v8的程序&#xff0c;特记录一下过程 环境&#xff1a;ubuntu18.04ros melodic 小知识&#xff1a;GPU并行计算能力高于CPU—B站UP主说的 Ubuntu可以安装多个版本的CUDA。如果某个程序的Pyorch需要不同版本的CUDA&#xff0c;不必删除之前的CUDA&#xff0c;…

wazuh环境配置

目录 一、wazuh的安装 1.1官方仓库安装 1.2虚拟机OVA安装 1.2.1 然后执行下面命令 1.2.2 这里还要下载脚本和config.yml配置文件&#xff0c;用来生成证书​编辑 1.2.3然后编辑config.yml文件&#xff0c;将下面的三个IP地址改为一样的 1.2.4运行./wazuh-certs-tool.sh以…

Linux Ubuntu系统安装OpenVPN服务

OpenVPN Ubuntu/Linux 服务端安装 官方文档&#xff1a;https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage 介绍 嘿&#xff0c;今天我们要探讨的话题是OpenVPN——那个让你在互联网上以安全又私密的方式冲浪的神奇工具。 首先&#xff0c;你可能会问&#xff…

机器学习模型的可解释性算法汇总

模型可解释性汇总 现有许多机器学习模型,尤其是深度学习模型,虽然他们的预测效果很好,但不具备很强的解释性,难以解释模型内部是如何作出决策的。这确实会带来很多问题: 信任度问题。如果一个模型无法解释自身,人们很难 100%信任它。这对于一些重要应用场景如医疗诊断来说尤其重…

【安全】原型链污染 - Code-Breaking 2018 Thejs

目录 准备工作 环境搭建 加载项目 复现 代码审计 payload 总结 准备工作 环境搭建 Nodejs BurpSuite 加载项目 项目链接 ① 下载好了cmd切进去 ② 安装这个项目 可以检查一下 ③运行并监听 可以看到已经在3000端口启动了 复现 代码审计 const fs require(fs) cons…

整理mongodb文档:聚合管道

个人博客 整理mongodb文档:聚合管道 个人博客&#xff0c;求关注&#xff0c;电脑版看体验更加&#xff0c;如果不够清晰&#xff0c;请指出来&#xff0c;谢谢 文章概叙 文章主要通过几个常用的聚合表达式来介绍聚合管道的使用&#xff0c;以及从索引的角度来介绍聚合管道…

使用lambda表达式提取共用代码使其更加简洁

1、在开发预下单接口访问并发问题出现需要加锁代码如下 RLock lock redissonClient.getLock(String.format(appointmentKey, activityId, studentId));try {boolean tryLock lock.tryLock(10, 20, TimeUnit.SECONDS);if (tryLock) {AppointmentMallOrderInfoDTO appointmentM…

【修改MAC地址工具】-TMAC

本文介绍修改MAC地址的神器小工具Technitium-MAC-Address-Changer 1. 下载地址 地址1&#xff1a; https://technitium.com/tmac/ 地址2&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1-jtwQ936gtepVWXKo_qwfg 提取码&#xff1a;2n1s 2. 安装 直接双击就可以安装…

iTwinCapture中文版原ContextCapture安装包以及安装教程

iTwinCapture软件安装教程 一、获取软件 中文安装包评论区或后台回复iTwinCapture 在App Store或Google Play搜索软件名称"iTwin Capture",下载安装软件。 您也可以在Bentley官网下载对应的iTwin Capture版本。 二、软件安装 点击下载后的安装包,根据提示完成安装…

NR SDAP

在NR系统的核心网中,业务不再以EPS承载的形式下发到接入网,而是以QoS flow的形式下发到接入网。在核心网引入QoS flow之后,可以提供比EPS承载更好的QoS粒度,从而更好地对IP数据流进行管理。一个QoS flow由一个或多干IP data stream聚合而成。因此,在核心网中,IP flow映射…

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…

JW0818近电报警芯片

JW0818 市电感应报警电路适用于电业人员和电信行业施工人员的安全保护用品–近电预警器 报警电路。 特别注意芯片引脚6&#xff0c;输出信号是方波&#xff0c;而不是高低电平&#xff1b;在产品开发过程遇到这个坑。

C++中的抽象类和接口

面向对象中的抽象概念 在进行面向对象分析时&#xff0c;会发现一些抽象的概念&#xff01; 图形的面积如何计算&#xff1f; 什么是抽象类&#xff1f; 在现实中需要知道具体的图像类型才能求面积&#xff0c;所以对概念上的 "图形" 求面积是没有意义的&#xff…

私有化部署即时通讯平台,30分钟替换钉钉和企业微信

随着企业对即时通讯和协作工具的需求不断增长&#xff0c;私有化部署的即时通讯平台成为企业的首选。WorkPlus作为有10余年行业深耕经验与技术沉淀品牌&#xff0c;以其安全高效的私有化部署即时通讯解决方案&#xff0c;帮助企业在30分钟内替换钉钉和企业微信。本文将深入探讨…

基于Jenkins自动打包并部署docker、PHP环境,ansible部署-------从小白到大神之路之学习运维第86天

第四阶段提升 时 间&#xff1a;2023年8月23日 参加人&#xff1a;全班人员 内 容&#xff1a; 基于Jenkins部署docker、PHP环境 目录 一、环境部署 &#xff08;一&#xff09;实验环境&#xff0c;服务器设置 &#xff08;二&#xff09;所有主机关闭防火墙和selinu…

Docker容器与虚拟化技术:Gitlab账户注册

目录 一、实验 1.gitlab 一、实验 1.gitlab (1) 概念 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 &#xff08;2&#xff09;官网 The DevSecOps Platform | GitLab &#xff08;3&#…

nginx 一个端口配置前后端分离项目访问

nginx 一个端口配置前后端分离项目访问 nginx配置 server {listen 8888;server_name _;location ~ .*\.(gif|jpg|jpeg|png|pdf|txt|zip|rar|7z|doc|docx|xls|xlsx|ppt|pptx|mp3|mp4)$ {root D:/platform/tomcat/apache-tomcat-9.0.31/webapps/resources;}location /api/…