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.mzph.cn/news/839538.shtml

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

相关文章

最新AI智能问答创作系统ChatGPT网站源码V4.20版本,GPTs、AI绘画、AI换脸、垫图混图+(GoMaxAI系统搭建部署教程文档)

Midjourney&#xff08;人工智能图像生成器&#xff09;&#xff0c;仅用了一年时间就已经火爆全球。它就像是一个想象力的助力器&#xff0c;总能带给人们一些不可思议的奇妙的作品。Midjourney虽然是一款最强的AI绘画工具&#xff0c;但对于很多人来说Midjourney怎么用&#…

使用JavaScript日历小部件和DHTMLX Gantt的应用场景(三)

DHTMLX Suite UI 组件库允许您更快地构建跨平台、跨浏览器 Web 和移动应用程序。它包括一组丰富的即用式 HTML5 组件&#xff0c;这些组件可以轻松组合到单个应用程序界面中。 DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足项目管理应用…

匝间冲击耐压试验仪产品介绍及工作原理

产品简介 武汉凯迪正大KD2684S匝间冲击耐压试验仪适用于电机、变压器、电器线圈等这些由漆包线绕制的产品。因漆包线的绝缘涂敷层本身存在着质量问题&#xff0c;以及在绕线、嵌线、刮线、接头端部整形、绝缘浸漆、装配等工序工艺中不慎而引起绝缘层的损伤等&#xff0c;都会造…

在线投票系统源码 网上投票平台创建 安全稳定 支持自定义投票规则+礼物道具功能

分享一款在线投票系统源码&#xff0c;是一款功能丰富、安全稳定的网络投票平台解决方案。通过本源码&#xff0c;用户可以轻松创建并管理各种在线投票活动&#xff0c;支持自定义投票规则&#xff0c;同时集成礼物道具功能&#xff0c;增强用户参与度和投票活动的趣味性&#…

2024 年 电工杯(B题)大学生数学建模挑战赛 | 平衡膳食食谱 | 数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 CS团队倾注了大量时间和心血&#xff0c;深入挖掘解决方案。通…

常用的框架——— Android UtilCode

AndroidUtilCode是一个功能强大且易于使用的Android库。该库封装了Android开发中经常使用的具备完整演示和单元测试的功能。经过使用其封装的API&#xff0c;能够大大提升开发效率。该程序主要由两个模块组成&#xff0c;utilcode&#xff08;一般在开发中使用&#xff09;和su…

【真人Q版手办风】线稿手绘+ AI绘图 Stable Diffusion 完整制作过程分享

大家好&#xff0c;我是设计师阿威。 今天给大家分享一篇【真人Q版卡通手办】风格的制作过程&#xff0c;话不多说&#xff0c;进入正题。 成品预览 手绘线稿 首先&#xff0c;我使用的是老款手绘软件【SAI】&#xff0c;用[钢笔工具]进行了人物的线稿Q版描绘。&#x1f447…

巨控无线通讯模块在煤化工皮带保护系统中的应用

一、项目介绍 近年来由于煤矿化工行业在实际生产过程中事故频发&#xff0c;国家安监部门自2022年起开展了为其三年的专项整治行动&#xff0c;皮带运输系统作为煤矿化工行业自动化系统的重要组成部分成为此次专项整治重点。 兖矿新疆能化有限公司作为山能集团在新疆分部的龙…

如何为海量计数场景设计缓存体系?no.34

计数常规方案 计数服务在互联网系统中非常常见&#xff0c;用户的关注粉丝数、帖子数、评论数等都需要进行计数存储。计数的存储格式也很简单&#xff0c;key 一般是用户 uid 或者帖子 id 加上后缀&#xff0c;value 一般是 8 字节的 long 型整数。 最常见的计数方案是采用缓存…

常见的100个Shell命令,超级实用!

在大多数的Linux和Unix系统、及其他类Unix系统中&#xff0c;Shell是用户与操作系统内核交互的主要方式。作为一种强大的命令行解释器&#xff0c;它也支持编程功能&#xff0c;用户可以写脚本来处理各种任务。 熟悉shell脚本&#xff0c;首先要对shell指令熟悉&#xff0c;今…

Vue3学习-用 vite@latest 初始化项目后,遇到无法识别 .vue 文件

引入app界面遇到 我的解决方案 1.根目录创建 env.d.ts&#xff0c;添加 declare module "*.vue" {import type { DefineComponent } from "vue"const vueComponent: DefineComponent<{}, {}, any>export default vueComponent }2.在 tsconfig.json…

基于ARM|DSP+FPGA+NVIDIA AI平台的摄像头ISP图像画质调试定制服务

基本框架及算法介绍 ISP(Image Signal Processor)&#xff0c;即图像处理&#xff0c;主要作用是对前端图像传感器输出的信号做后期处理&#xff0c;主要功能有线性纠正、噪声去除、坏点去除、内插、白平衡、自动曝光控制等&#xff0c;依赖于ISP才能在不同的光学条件下都…

scroll-snap-type——有滚动容器下吸附至吸附点的严格程度——css基础

scroll-snap-type有滚动容器下吸附至吸附点的严格程度&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-snap-type 此属性不为吸附点指定任何确切的动画或运行规律&#xff0c;留待用户代理处理。 //不吸附 scroll-snap-type:none; //表示吸附轴的关键字…

IPD在卷烟工业企业研发管理中应用

一、 什么是IPD IPD是Integrated Product Development几个英文单词的缩写&#xff0c;译成汉语就是“集成产品研发”&#xff0c;是上世纪九十年代以来世界上盛行的企业产品研发管理的成功模式。下文中汉捷咨询对IPD的由来进行分享。 最先将IPD付诸实践的是美国IBM公司。1992…

AI绘图副业创收,热门擦边变现赛道怎么玩?网友:瑟瑟才是人类前进的动力!

大家好&#xff0c;我是设计师阿威 今天给大家介绍一个用 AI 搞擦边的变现赛道 而且可以说是0 成本变现的 现在真的越来越多的人都想 0 成本变现&#xff0c;那么 0 成本到底能不能变现&#xff0c;变现的上下限又是多少&#xff1f; 今天这个案例就可以很好的进行说明 可以…

路由引入实验(华为)

思科设备参考&#xff1a;路由引入实验&#xff08;思科&#xff09; 技术简介 路由引入技术在网络通信中起着重要的作用&#xff0c;能够实现不同路由协议之间的路由传递&#xff0c;并在路由引入时部署路由控制&#xff0c;实现路径或策略的控制 实验目的 不同的路由协议之…

python web自动化(Pytest实战)

1.UnitTest框架与Pytest框架对⽐ 1&#xff09; unittest框架介绍 Unittest则是Python语⾔的标准单元测试框架。 Unittest⽀持⾃动化测试&#xff0c;测试⽤例的初 始化、关闭和测试⽤例的聚合等功能&#xff0c;它有⼀个很重要的特性&#xff…

深度学习-转置卷积

转置卷积 转置卷积&#xff08;Transposed Convolution&#xff09;&#xff0c;也被称为反卷积&#xff08;Deconvolution&#xff09;&#xff0c;是深度学习中的一种操作&#xff0c;特别是在卷积神经网络&#xff08;CNN&#xff09;中。它可以将一个低维度的特征图&#x…

Java面试八股之有哪些线程安全的集合类

Java中有哪些线程安全的集合类 在Java中&#xff0c;并非所有的集合类都是线程安全的&#xff0c;但在多线程环境下&#xff0c;确保集合操作的线程安全性至关重要。以下是几个典型的线程安全集合类&#xff1a; Vector: 类似于ArrayList&#xff0c;但它是线程安全的。它通过…

搭建python环境

要想能够进行python开发&#xff0c;就需要搭建好python的环境。 需要安装的环境主要是两个部分&#xff1a; 运行环境&#xff1a;python 开发环境&#xff1a;pycharm 官方网站&#xff1a;https&#xff1a;//www.python.org pycharm软件调节字体大小 pycharm 软件调节背…