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 优点
- 内存效率高:
SparseArray
使用并行数组,避免了HashMap
中对象封装导致的内存开销,特别适合键是整数的情况。 - 高效查找:通过二分查找在键数组中定位元素,查找时间复杂度为 O(log N)。
- 自动扩展:
GrowingArrayUtils
确保数组在需要时自动扩展,减少手动管理数组大小的麻烦。 - 避免自动装箱:与
HashMap<Integer, Object>
不同,SparseArray
直接使用int
类型键,避免了自动装箱的开销。
4.2 缺点
- 不适合频繁删除操作:删除操作只是将值标记为 “已删除”,需要额外的垃圾回收步骤,这可能影响性能。
- 键必须是整数:只能用于整数键的情况,不够通用。
- 固定容量扩展:数组扩展是按固定策略进行的(当前大小的倍数扩展),在某些极端情况下可能导致不必要的内存浪费。
五、使用场景
5.1 适用场景
- 大量键值对:适用于需要存储大量键值对且键为整数的场景,如缓存、映射关系等。
- 高性能要求:适合内存敏感的应用,如低端设备上的应用、实时应用等。
- 稀疏数据集:特别适用于键值对稀疏分布的场景。
5.2 不适用场景
- 频繁插入删除:如果应用需要频繁插入和删除操作,
SparseArray
的性能可能不如HashMap
。 - 非整数键:如果键不是整数,
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
存储和管理用户会话。通过addSession
、getSession
、removeSession
等方法,可以高效地管理会话数据。cleanUpSessions
方法演示了如何清理过期会话,同时展示了删除标记和垃圾回收机制。
七、总结
SparseArray
是 Android 提供的一个高效数据结构,用于整数键值对的存储和查找。它通过优化内存使用和查找性能,特别适合在性能敏感和内存有限的应用中使用。通过理解其实现原理和优缺点,可以在适当的场景中充分利用其优势。
SparseArray
是一种优化的稀疏数组,适用于键为整数的场景。它的实现通过两个并行数组和二分搜索来提高查找和存储的效率,避免了使用HashMap
可能带来的内存开销。
- 存储:使用两个并行数组分别存储键和值。
- 查找:通过二分搜索快速定位键的位置。
- 垃圾回收:延迟删除机制,通过标记删除和垃圾回收减少数组重新分配次数。
- 性能优化:通过ViewHolder模式和减少对象分配,
SparseArray
在大量数据操作时性能表现良好。
欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力 |
相关文章:

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() 二、使用…...

学术图表的基本配色方法
不论是商业图表还是专业图表,图表的配色都极其关键。图表配色主要有彩色和黑白两种配色方案。刘万祥老师曾提出: “在我看来,普通图表与专业图表的差别,很大程度就体现在颜色运用上。” 对于科学图表,大部分国内的期…...

【学习笔记】Webpack5(Ⅱ)
Webpack 3、高级篇 3.1、提升开发体验 —— SourceMap 3.2、提升打包速度 3.2.1 HotModuleReplacement 3.2.2 OneOf 3.2.3 Include / Exclude 3.2.4 Cache 3.2.5 Thread 3.3、减少代码体积 …...
oracle碎片整理
1、move碎片整理 1) DECLARE tmp_val VARCHAR2 (500); BEGIN FOR REC IN (SELECT TABLE_NAME FROM USER_TABLES ) LOOP tmp_val:=ALTER TABLE || REC.TABLE_NAME || MOVE; BEGIN EXECUTE IMMEDIATE tmp_val; DBMS_OUTPUT.ENABLE(buffer_size => null); DBMS_OUTPUT.put_l…...

民国漫画杂志《时代漫画》第15期.PDF
时代漫画15.PDF: https://url03.ctfile.com/f/1779803-1247458444-8befd8?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了,截止1937年6月战争来临被迫停刊共发行了39期。 ps:资源来源网络!...

Alamofire常见GET/POST等请求方式的使用,响应直接为json
Alamofire 官方仓库地址:https://github.com/Alamofire/Alamofire xcode中安装和使用:swift网络库Alamofire的安装及简单使用,苹果开发必备-CSDN博客 Alamofire是一个基于Swift语言开发的优秀网络请求库。它封装了底层的网络请求工作&…...

三分钟一条AI小和尚视频 ,日引300+创业粉。单日变现四位数 全套工具
经过六个月的不懈努力和无数次的尝试错误,我终于找到了一个高效引流和积累粉丝的新策略,并愿意与大家无私分享。这一次,我将详尽地介绍这个方法,建议朋友们多次观看以彻底掌握其精髓。 简而言之,该策略主要依托于AI绘…...

vue3中表格中通过判断某个字段来设置对应按钮和消息提示的disabled展示
vue3中表格中通过判断某个字段来设置对应按钮和消息提示的disabled展示 一、前言1.代码案例2.效果展示 一、前言 当使用 Vue 3 和 Element UI 的 el-table 组件时,你可以通过判断字段的值来设置对应的 el-button 的 disabled 属性和消息提示。下面是一个简单的示例…...

产品经理-交互说明撰写(八)
1. 交互说明 交互说明可以看做是交互设计师或者产品经理输出的最核心的”产品“交互说明面向的”用户“是下游的同事 ⇒ UI设计师、开发工程师、测试工程师 2. 基本交互形式 2.1 页面交互 2.2 元素控件交互 3. 交互说明主要包括以下3个维度 3.1 页面流程(页面之…...
Rust:struct 与字节序列的相互转换
在 Rust 中,将结构体(struct)与字节序列(Vec<u8>)相互转换的常见方法是使用序列化和反序列化库。Rust 有一个流行的序列化库叫做 serde,它支持多种数据格式。为了将结构体转换为字节序列,…...
在https的系统中挂载其他http系统的画面的解决方案
目录 1.问题及说明 2.解决方案及示例 3.总结 1.问题及说明 A系统使用了https,在A系统中挂载B系统的http的画面,会报错如下: Mixed Content: The page at https://beef.zz.com/front/#/biz/cultivationList/cultivationDetails/5dbf836751…...

mysql存储比特位
一、介绍 二、SQL CREATE TABLE bits_table (id INT PRIMARY KEY AUTO_INCREMENT,bit_value BIGINT UNSIGNED );-- 插入一个 8 位的 BIT 值 INSERT INTO bits_table (bit_value) VALUES (B10101010);-- 查询并格式化输出 SELECT id,bit_value,CONCAT(b, LPAD(BIN(bit_value),…...
Lua中table.sort()使用方式
table.sort(tab,compare) 参数如下: tab:表名 compare:比较规则函数名 简略写法: a {1,2,3} table.sort(a,function(a,b) return a>b end) compare这个参数是一个函数,它有两个参数,你可以理解为表中的两个不同元素&…...
数组与指针声明小问题
1、int *p &a; 是 C 语言中的一条语句,它涉及指针的声明和初始化。让我们逐步解释这一行代码的含义: int *p:这是一个指针声明。它声明了一个名为 p 的变量,该变量是一个指向 int 类型数据的指针。 &a:这是取…...

【Java】手把手学会数组的使用
数组的基本用法 创建数组 基本语法: // 动态初始化 数据类型 [] 数组名称 new 数据类型 [] { 初始化数据 }; // 静态初始化 数据类型 [] 数组名称 { 初始化数据 }; 代码示例: int[] array1 {1,2,3,4,5};int[] array2 new int[]…...

音视频开发9 FFmpeg 解复用框架--如何将一个影音文件(mp4文件/wav文件) 最终播放起来
一,播放器框架 二 常用音视频术语 容器/文件(Conainer/File): 即特定格式的多媒体文件, 比如mp4、flv、mkv等。 媒体流(Stream): 表示时间轴上的一段连续数据࿰…...

vue实现页面渲染时候执行某需求
1. 前言 在之前的项目中,需要实现一个监控token是否过期从而动态刷新token的功能,然而在登录成功后创建的监控器会在浏览器刷新点击或者是通过导航栏输入网址时销毁... 2. 试错 前前后后始过很多方法,在这里就记录一下也许也能为各位读者排…...

Python小游戏——俄罗斯方块
文章目录 项目介绍环境配置代码设计思路1.初始化和导入库:2.定义颜色和屏幕尺寸:3.定义游戏逻辑:4.游戏循环: 源代码效果图 项目介绍 俄罗斯方块游戏是一款经典的益智游戏,玩家通过旋转和移动各种形状的方块ÿ…...

Moto和Inter字节序
inter: 低地址按照start_bit位放低字节依次往高字节填充 MotoLsb: 低地址按照start_bit位放高字节,依次往低字节填充MotoMsb:高字节按照start_bit位放低地址,依次往高字节填充...

外汇天眼:野村证券和Laser Digital与GMO互联网集团合作发行日元和美元稳定币
野村控股和Laser Digital将与GMO互联网集团合作,在日本探索发行日元和美元稳定币。GMO互联网集团的美国子公司GMO-Z.com Trust Company, Inc. 在纽约州金融服务部的监管框架下,在以太坊、恒星币和Solana等主要区块链上发行稳定币。GMO-Z.com Trust Compa…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...