Android 性能为王时代SparseArray和HashMap一争高下

文章目录

      • 一、`SparseArray` 源码分析
        • 1. **类定义和构造函数**
        • 2. **基本方法**
          • 2.1 `put(int key, E value)`
          • 2.2 `get(int key)`
          • 2.3 `delete(int key)`
          • 2.4 `removeAt(int index)`
          • 2.5 `gc()`
          • 2.6 `size()`
          • 2.7 `keyAt(int index)` 和 `valueAt(int index)`
        • 3. **辅助方法**
          • 3.1 `binarySearch()`
      • 二、使用示例
      • 三、详细实现分析
        • 3.1 `ContainerHelpers` 类
        • 3.2 `GrowingArrayUtils` 类
      • 四、优缺点
        • 4.1 优点
        • 4.2 缺点
      • 五、使用场景
        • 5.1 适用场景
        • 5.2 不适用场景
      • 六、实际使用示例
      • 七、总结

SparseArray 是 Android 中一种高效的数据结构,用于将整数键映射到对象。它与 HashMap 类似,但为了节省内存,使用两个并行数组来存储键和值,并采用二分搜索进行查找。以下是对 SparseArray 源码的详细分析。

一、SparseArray 源码分析

1. 类定义和构造函数

SparseArray 是一个泛型类,继承自 Object

public class SparseArray<E> implements Cloneable {private static final Object DELETED = new Object();private boolean mGarbage = false;private int[] mKeys;private Object[] mValues;private int mSize;public SparseArray() {this(10);  // 默认初始容量为10}public SparseArray(int initialCapacity) {if (initialCapacity == 0) {mKeys = EmptyArray.INT;mValues = EmptyArray.OBJECT;} else {mKeys = new int[initialCapacity];mValues = new Object[initialCapacity];}mSize = 0;}
}
2. 基本方法
2.1 put(int key, E value)

将键值对插入 SparseArray 中。

public void put(int key, E value) {int i = ContainerHelpers.binarySearch(mKeys, mSize, key);if (i >= 0) {mValues[i] = value;} else {i = ~i;if (i < mSize && mValues[i] == DELETED) {mKeys[i] = key;mValues[i] = value;return;}if (mGarbage && mSize >= mKeys.length) {gc();i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);}mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);mSize++;}
}
2.2 get(int key)

通过键获取值,如果不存在则返回默认值 null

public E get(int key) {return get(key, null);
}public E get(int key, E valueIfKeyNotFound) {int i = ContainerHelpers.binarySearch(mKeys, mSize, key);if (i < 0 || mValues[i] == DELETED) {return valueIfKeyNotFound;} else {return (E) mValues[i];}
}
2.3 delete(int key)

删除键值对。

public void delete(int key) {int i = ContainerHelpers.binarySearch(mKeys, mSize, key);if (i >= 0) {if (mValues[i] != DELETED) {mValues[i] = DELETED;mGarbage = true;}}
}
2.4 removeAt(int index)

删除指定索引处的键值对。

public void removeAt(int index) {if (mValues[index] != DELETED) {mValues[index] = DELETED;mGarbage = true;}
}
2.5 gc()

垃圾回收,清理被标记删除的元素。

private void gc() {int n = mSize;int o = 0;int[] keys = mKeys;Object[] values = mValues;for (int i = 0; i < n; i++) {Object val = values[i];if (val != DELETED) {if (i != o) {keys[o] = keys[i];values[o] = val;values[i] = null;}o++;}}mGarbage = false;mSize = o;
}
2.6 size()

返回键值对的数量。

public int size() {if (mGarbage) {gc();}return mSize;
}
2.7 keyAt(int index)valueAt(int index)

通过索引获取键或值。

public int keyAt(int index) {if (mGarbage) {gc();}return mKeys[index];
}public E valueAt(int index) {if (mGarbage) {gc();}return (E) mValues[index];
}
3. 辅助方法
3.1 binarySearch()

二分搜索,用于在有序数组中查找元素。

public static int binarySearch(int[] array, int size, int value) {int lo = 0;int hi = size - 1;while (lo <= hi) {final int mid = (lo + hi) >>> 1;final int midVal = array[mid];if (midVal < value) {lo = mid + 1;} else if (midVal > value) {hi = mid - 1;} else {return mid; // value found}}return ~lo;  // value not present
}

二、使用示例

以下是SparseArray的简单使用示例:

SparseArray<String> sparseArray = new SparseArray<>();
sparseArray.put(1, "One");
sparseArray.put(2, "Two");
sparseArray.put(3, "Three");// 获取值
String value = sparseArray.get(2); // "Two"// 删除值
sparseArray.delete(3);// 获取键和值
for (int i = 0; i < sparseArray.size(); i++) {int key = sparseArray.keyAt(i);String val = sparseArray.valueAt(i);Log.d("SparseArray", "Key: " + key + ", Value: " + val);
}

通过这种方式,我们可以高效地管理键为整数的键值对,特别适用于性能敏感的应用场景。

继续深入分析SparseArray的实现细节,并探讨其优缺点和使用场景。

三、详细实现分析

3.1 ContainerHelpers

ContainerHelpers 提供了 SparseArray 使用的二分搜索功能。

public class ContainerHelpers {public static int binarySearch(int[] array, int size, int value) {int lo = 0;int hi = size - 1;while (lo <= hi) {final int mid = (lo + hi) >>> 1;final int midVal = array[mid];if (midVal < value) {lo = mid + 1;} else if (midVal > value) {hi = mid - 1;} else {return mid; // value found}}return ~lo;  // value not present}
}

该方法通过二分查找在一个有序整数数组中定位特定值的位置。如果找到匹配值,则返回其索引;否则返回插入点的反码(即 ~lo)。

3.2 GrowingArrayUtils

GrowingArrayUtils 用于在数组中插入元素并自动扩展数组容量。

public class GrowingArrayUtils {public static int[] insert(int[] array, int currentSize, int index, int element) {if (currentSize + 1 > array.length) {int[] newArray = new int[growSize(currentSize)];System.arraycopy(array, 0, newArray, 0, index);newArray[index] = element;System.arraycopy(array, index, newArray, index + 1, currentSize - index);return newArray;} else {System.arraycopy(array, index, array, index + 1, currentSize - index);array[index] = element;return array;}}public static <T> T[] insert(T[] array, int currentSize, int index, T element) {if (currentSize + 1 > array.length) {@SuppressWarnings("unchecked")T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(), growSize(currentSize));System.arraycopy(array, 0, newArray, 0, index);newArray[index] = element;System.arraycopy(array, index, newArray, index + 1, currentSize - index);return newArray;} else {System.arraycopy(array, index, array, index + 1, currentSize - index);array[index] = element;return array;}}private static int growSize(int currentSize) {return currentSize <= 4 ? 8 : currentSize * 2;}
}

该类提供了向数组中插入元素的方法,如果数组已满,则会扩展数组容量。growSize 方法根据当前大小决定扩展大小。

四、优缺点

4.1 优点
  1. 内存效率高SparseArray 使用并行数组,避免了 HashMap 中对象封装导致的内存开销,特别适合键是整数的情况。
  2. 高效查找:通过二分查找在键数组中定位元素,查找时间复杂度为 O(log N)。
  3. 自动扩展GrowingArrayUtils 确保数组在需要时自动扩展,减少手动管理数组大小的麻烦。
  4. 避免自动装箱:与 HashMap<Integer, Object> 不同,SparseArray 直接使用 int 类型键,避免了自动装箱的开销。
4.2 缺点
  1. 不适合频繁删除操作:删除操作只是将值标记为 “已删除”,需要额外的垃圾回收步骤,这可能影响性能。
  2. 键必须是整数:只能用于整数键的情况,不够通用。
  3. 固定容量扩展:数组扩展是按固定策略进行的(当前大小的倍数扩展),在某些极端情况下可能导致不必要的内存浪费。

五、使用场景

5.1 适用场景
  1. 大量键值对:适用于需要存储大量键值对且键为整数的场景,如缓存、映射关系等。
  2. 高性能要求:适合内存敏感的应用,如低端设备上的应用、实时应用等。
  3. 稀疏数据集:特别适用于键值对稀疏分布的场景。
5.2 不适用场景
  1. 频繁插入删除:如果应用需要频繁插入和删除操作,SparseArray 的性能可能不如 HashMap
  2. 非整数键:如果键不是整数,SparseArray 无法使用。

六、实际使用示例

下面是一个实际应用场景中的示例,用于存储和查找用户会话数据:

public class SessionManager {private SparseArray<Session> sessionSparseArray;public SessionManager() {sessionSparseArray = new SparseArray<>();}public void addSession(int sessionId, Session session) {sessionSparseArray.put(sessionId, session);}public Session getSession(int sessionId) {return sessionSparseArray.get(sessionId);}public void removeSession(int sessionId) {sessionSparseArray.delete(sessionId);}public int getSessionCount() {return sessionSparseArray.size();}// 清理被标记删除的会话public void cleanUpSessions() {for (int i = 0; i < sessionSparseArray.size(); i++) {int key = sessionSparseArray.keyAt(i);Session session = sessionSparseArray.get(key);if (session.isExpired()) {sessionSparseArray.removeAt(i);}}}
}class Session {private long creationTime;private long expiryTime;public Session(long creationTime, long expiryTime) {this.creationTime = creationTime;this.expiryTime = expiryTime;}public boolean isExpired() {return System.currentTimeMillis() > expiryTime;}
}

在这个示例中,SessionManager 使用 SparseArray 存储和管理用户会话。通过addSessiongetSessionremoveSession等方法,可以高效地管理会话数据。cleanUpSessions 方法演示了如何清理过期会话,同时展示了删除标记和垃圾回收机制。

七、总结

SparseArray 是 Android 提供的一个高效数据结构,用于整数键值对的存储和查找。它通过优化内存使用和查找性能,特别适合在性能敏感和内存有限的应用中使用。通过理解其实现原理和优缺点,可以在适当的场景中充分利用其优势。

SparseArray 是一种优化的稀疏数组,适用于键为整数的场景。它的实现通过两个并行数组和二分搜索来提高查找和存储的效率,避免了使用HashMap可能带来的内存开销。

  • 存储:使用两个并行数组分别存储键和值。
  • 查找:通过二分搜索快速定位键的位置。
  • 垃圾回收:延迟删除机制,通过标记删除和垃圾回收减少数组重新分配次数。
  • 性能优化:通过ViewHolder模式和减少对象分配,SparseArray 在大量数据操作时性能表现良好。
欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力

在这里插入图片描述

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

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

相关文章

【SpringCloud】负载均衡

目录 负载均衡什么是负载均衡生活场景为什么需要负载均衡负载均衡手段负载均衡总的来说有两种实现手段负载均衡具体可以通过多种手段来实现 SpringCloud中的负载均衡组件Ribbon VS Nginx负载均衡区别集中式LB进程内LB RibbonRibbon的工作原理Ribbon在工作时分成两步 使用1.提供…

昔日辉煌不再,PHP老矣,尚能饭否?

导语 | 近期 TIOBE 最新指数显示&#xff0c;PHP 的流行度降至了历史最低&#xff0c;排在第 17 名&#xff0c;同时&#xff0c;在年度 Stack Overflow 开发者调查报告中&#xff0c;PHP 在开发者中的受欢迎程度已经从之前的约 30% 萎缩至现在的 18%。“PHP 是世界上最好的语言…

【开源】大学生竞赛管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、系统介绍 学生管理模块 教师管理模块 竞赛信息模块 竞赛报名模块 二、系统截图 三、核心代码 一、系统介绍 基于Vue.js和SpringBoot的大学生竞赛管理系统&#xff0c;分为管理后台和用户网页端&#xff0c;可以给管理员、学生和教师角色使用&#xff0c;包括学…

【Flutter】Dialog组件PageView组件

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Flutter学习 &#x1f320; 首发时间&#xff1a;2024年5月27日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; 目…

如何确保大模型 RAG 生成的信息是基于可靠的数据源?

在不断发展的人工智能 (AI) 领域中&#xff0c;检索增强生成 (RAG) 已成为一种强大的技术。 RAG 弥合了大型语言模型 (LLM) 与外部知识源之间的差距&#xff0c;使 AI 系统能够提供更全面和信息丰富的响应。然而&#xff0c;一个关键因素有时会缺失——透明性。 我们如何能够…

mysql中单表查询的成本

大家好。我们知道MySQL在执行一个查询时&#xff0c;经常会有多个执行方案&#xff0c;然后从中选取成本最低或者说代价最低的方案去真正的执行查询。今天我们来聊一聊单表查询的成本。 那么到底什么是成本呢&#xff1f;这里我们说的成本或者代价是由两方面组成的&#xff1a…

vscode插件-03 PHP

PHP Intelephense 如果php在远程计算机上&#xff0c;要把插件安装在远程&#xff0c;而不是本地。 这个插件&#xff0c;要求php版本大于7&#xff0c;且设置环境变量&#xff08;好像不一定要设置&#xff09;。 设置里面搜索php.executablePath&#xff0c;打开setting.js…

element ui 的el-input输入一个字后失去焦点,需重新点击输入框才能再次输入

解决方案&#xff1a; 我是form表单嵌套表格&#xff0c;里面的el-input输入框&#xff0c;输入第一个值的时候会突然失去焦点&#xff0c;需要再次点击输入框才能正常输入&#xff0c;原因是table的key值&#xff0c;需要改成正常的index即可&#xff0c;如果你是循环的&…

ESP32入门:1、VSCode+PlatformIO环境搭建

文章目录 背景安装vscode安装配置中文 安装Platform IO安装PIO 新建ESP32工程参考 背景 对于刚接触单片机的同学&#xff0c;使用vscodeplatformIO来学习ESP32是最方便快捷的&#xff0c;比IDF框架简单&#xff0c;且比arduino文件管理性能更好。但是platformIO安装较为麻烦&a…

gnocchi学习小结

背景 总结gnocchi 4.4版本gnocchi-metricd工作流程 入口 gnocchi.cli.metricd metricd stop after processing metric默认为0&#xff0c;调servicemanager run MetricdServiceManager __init__ 服务逻辑封装到MetricdServiceManager初始化中 主要由MetricProcessor, Met…

【方法】ZIP压缩文件的密码如何设置和取消?

ZIP是一种常见的压缩文件格式&#xff0c;今天来分享一下&#xff0c;ZIP压缩文件如何设置密码保护&#xff0c;以及如何取消密码&#xff0c;不清楚的小伙伴一起来看看吧&#xff01; 设置ZIP文件密码&#xff1a; 想要给ZIP压缩包设置密码&#xff0c;需要用到支持ZIP格式的…

香橙派 Kunpeng Pro使用教程:从零开始打造个人私密博客

一、引言 在这个日益互联的世界中&#xff0c;单板计算机已经成为创新和个性化解决方案的重要载体。而在单板计算机领域&#xff0c;香橙派 Kunpeng Pro凭借其强大的性能和灵活的应用潜力&#xff0c;正逐渐吸引着全球开发者和技术爱好者的目光。 作为一款集成了华为的鲲鹏处…

【AD21】文件的整理

当所有文件输出完成后&#xff0c;需要对不同的文件去做一个整理&#xff0c;方便后续工作的交接。 在项目工程文件夹下新建名称为BOM、SMT、PRJ、Gerber和DOC的文件夹。 BOM文件夹存放BOM表发给采购人员。SMT文件夹存放装配图文件和坐标文件发给贴片厂。PRJ文件夹存放工程文件…

AI大模型探索之路-实战篇4:深入DB-GPT数据应用开发框架调研

目录 前言一、DB-GPT总体概述二、DB-GPT关键特性1、私域问答&数据处理&RAG2、多数据源&GBI3、多模型管理4、自动化微调5、Data-Driven Multi-Agents&Plugins6、隐私安全 三、服务器资源准备1、创建实例2、打开jupyterLab 四、DB-GPT启动1、激活 conda 环境2、切…

2024年03月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,共50分) 第1题 期末考试结束了,全班的语文成绩都储存在列表score中,班主任老师请小明找到全班最高分,小明准备用Python来完成,以下哪个选项,可以获取最高分呢?( ) A:min(score) B:max(score) C:sco…

夏日将至,给手机装个“液冷”降温可行吗?

夏天出门在外&#xff0c;手机总是更容易发热&#xff0c;尤其是顶着大太阳用手机的时候&#xff0c;更是考验手机的散热能力。如果你也是一个对手机体验有追求的人&#xff0c;比较在意手机的温度&#xff0c;那么可以考虑入手一个微泵液冷手机壳。 【什么是微泵液冷壳&#…

【Spring Security + OAuth2】OAuth2

Spring Security OAuth2 第一章 Spring Security 快速入门 第二章 Spring Security 自定义配置 第三章 Spring Security 前后端分离配置 第四章 Spring Security 身份认证 第五章 Spring Security 授权 第六章 OAuth2 文章目录 Spring Security OAuth21、OAuth2简介1.1、OAu…

数据结构(三)循环链表

文章目录 一、循环链表&#xff08;一&#xff09;概念&#xff08;二&#xff09;示意图&#xff08;三&#xff09;操作1. 创建循环链表&#xff08;1&#xff09;函数声明&#xff08;2&#xff09;注意点&#xff08;3&#xff09;代码实现 2. 插入&#xff08;头插&#x…

vue3 3D炫酷模型banner图

项目场景&#xff1a; 在官网首页展示3D炫酷动画模型&#xff0c;让整个模型都展示出来。 问题描述 主要是3D动画的展示效果&#xff0c;有些3d模型网站可以从51建模网站中获取。 案例代码&#xff1a; <script setup> import * as imgs from ../units/img import { o…

如果查看svn的账号和密码

一、找到svn存放目录&#xff08;本地默认存放SVN用户信息的目录为&#xff1a;C:\Users\Administrator\AppData\Roaming\Subversion\auth\svn.simple&#xff09;每个人的电脑环境不一样&#xff0c;因人而异。 如果找不到直接搜索svn.simple 二、下载密码查看工具 链接: 百…