数据结构(二): 轻量级键值对 SparseArray
SparseArray是Android framework中提供的轻量级的键值对数据结构,我们知道空间和效率从来都是相悖的,SparseArray的实现正是以时间来换取空间效率,适合小规模数据的存储。
下面来了解下SparseArray的特点,使用,并分析部分源码。
一、特点
SparseArray以键值对的形式保存数据,key是int类型,并且是唯一的不允许重复的key,而value可以是任何object。
SparseArray是轻量级的,使用2个数组分别保存key和value,并通过数组下标来对应关联。key数组是保持有序的。
优点
相比HashMap更加节省内存空间,数据存储只依赖key和value2个数组,数组空间是可复用的,数据的存储密度较高。
因为key数组是有序的,通过key获取value相对高效。
缺点:
key数组是保持有序的,在插入和查找时,通过二分法确定位置key的下标,比较耗时;
插入删除等操作可能会移动数组数据。
综合来说,SparseArray适用于小数据量的键值对场景。数据量达到几百时,效率优势和HashMap相比已不明显。
二、使用方式
插入
1. SparseArray初始化时需要指定存储数据的具体类型
2. 使用put方法,插入key和对应的value
SparseArray<String> strArray = new SparseArray<>();
strArray.put(3, "DDDD");
strArray.put(1, "AAAA");
strArray.put(4, "CCCC");
strArray.put(2, "BBBB");
遍历
SparseArray没有实现Iterable,只能通过手动循环遍历:
for(int i = 0;i<strArray.size();i++){
String value = strArray.valueAt(i);
//do sth
}
插入的4条数据,打印是根据key值从小到大的顺序输出的,这也印证了在执行插入之后,key数组是保持有序的:
value at 0:AAAA
value at 1:BBBB
value at 2:DDDD
value at 3:CCCC
删除
删除元素的方法有2个:
1. removeAt,删除指定下标的value。
2. delete通过key删除对应的value。
从以上插入的4个数据中删除其中2个:
strArray.remove(3);//删除key为3的值,即DDDD
strArray.removeAt(0);//删除数组中的第一个值,即AAAA
结果只剩下BBBB和CCCC:
value at 0:BBBB
value at 1:CCCC
通过key获取元素
通过get方法传入key来获取对应的value:
Log.i(TAG, "get 1:" +strArray.get(1));
Log.i(TAG, "get 4:" +strArray.get(4));
输出:
get 1:null //key=1的值为AAAA,已经被删除,因此返回null
get 4:CCCC
查找
指定key或者value来查找其下标值:
1. indexOfKey二分法从mKeys数组中查找key的下标
2. indexOfValue 遍历查找mValues数组中value元素的下标。
Log.i(TAG, "index of key 4:" +strArray.indexOfKey(4));
Log.i(TAG, "index of value BBBB:" +strArray.indexOfValue("BBBB"));
输出:
key=4的值,数组下标为1,
BBBB在value数组中下标为0
index of key 4:1
index of value BBBB:0
三、源码学习
SparseArray采用时间换取空间的方式来提高手机App的运行效率,这也是其与HashMap的区别;HashMap通过空间换取时间,查找迅速;HashMap中当table数组中内容达到总容量0.75时,则扩展为当前容量的两倍。
- SparseArray的key为int,value为Object
- 在Android中,数据长度小于千时,用于替换HashMap
- 相比与HashMap,其采用 时间换空间 的方式,使用更少的内存来提高手机APP的运行效率(HashMap中当table数组中内容达到总容量0.75时,则扩展为当前容量的两倍
下面我们来对其源码进行学习:
构造方法
// 构造方法
public SparseArray() {
this(10);
} // 构造方法
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
mKeys = EmptyArray.INT;
mValues = EmptyArray.OBJECT;
} else {
// key value各自为一个数组,默认长度为10
mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
mKeys = new int[mValues.length];
}
mSize = 0;
}
源码说明:
- SparseArray构造方法中,创建了两个数组mKeys、mValues分别存放int与Object,其默认长度为10。
put(int key, E value)
public void put(int key, E value) {
// 二分查找,key在mKeys列表中对应的index
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
// 如果找到,则直接赋值
if (i >= 0) {
mValues[i] = value;
}
// 找不到
else {
// binarySearch方法中,找不到时,i取了其非,这里再次取非,则非非则正
i = ~i;
// 如果该位置的数据正好被删除,则赋值
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
// 如果有数据被删除了,则gc
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
// 插入数据,增长mKeys与mValues列表
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
源码说明:
- 因为key为int,不存在hash冲突
- mKeys为有序列表,通过二分查找,找到要插入的key对应的index (这里相对于查找hash表应该算是费时间吧,但节省了内存,所以是 时间换取了空间)
- 通过二分查找到的index,将Value插入到mValues数组的对应位置
get(int key)
// 通过key查找对应的value
public E get(int key) {
return get(key, null);
}
// 通过key查找对应的value
public E get(int key, E valueIfKeyNotFound) {
// mKeys数组中采用二分查找,找到key对应的index
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
// 没有找到,则返回空
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
// 找到则返回对应的value
return (E) mValues[i];
}
}
源码说明:
- 每次调用get,则需经过一次mKeys数组的二分查找,因此mKeys数组越大则二分查找的时间就越长,因此SparseArray在大量数据,千以上时,会效率较低
ContainerHelpers.binarySearch(mKeys, mSize, key) 二分查找
// array为有序数组
// size数组中内容长度
// value要查找的值
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];
// 如果中间元素小于要查找元素,则midIndex赋值给 lo
if (midVal < value) {
lo = mid + 1;
}
// 如果中间元素大于要查找元素,则midIndex赋值给 hi
else if (midVal > value) {
hi = mid - 1;
}
// 找到则返回
else {
return mid; // value found
}
}
// 找不到,则lo 取非
return ~lo; // value not present
}
数据结构(二): 轻量级键值对 SparseArray的更多相关文章
- es6初级之解构----之二 及 键值反转实现
1.解构: 不定参数,扩展表达式 let arr = [100, 201, 303, 911]; let [one, ...others] = arr; console.log(others.leng ...
- YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能
YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. 一.动态属性扩展 在实际的开发过程中,你肯定会遇到数 ...
- Show 一下最新的动态属性扩展功能与键值生成器功能
Show 一下最新的动态属性扩展功能与键值生成器功能 YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. ...
- Android : 输入设备键值从底层到应用层的映射流程
一.Android输入子系统简介: Android输入事件的源头是位于/dev/input/下的设备节点,而输入系统的终点是由WMS管理的某个窗口.最初的输入事件为内核生成的原始事件,而最终交付给窗口 ...
- php 二位数组按某个键值排序
$arr=[ array( 'name'=>'小坏龙', 'age'=>28 ), array( 'name'=>'小坏龙2', 'age'=>14 ), array( 'na ...
- 【原】Learning Spark (Python版) 学习笔记(二)----键值对、数据读取与保存、共享特性
本来应该上周更新的,结果碰上五一,懒癌发作,就推迟了 = =.以后还是要按时完成任务.废话不多说,第四章-第六章主要讲了三个内容:键值对.数据读取与保存与Spark的两个共享特性(累加器和广播变量). ...
- PHP 按二维数组的键值排序
/** * 按二维数组的键值排序 * @param unknown $array 二维数组 * @param unknown $key 二维数组的键值 * @param string $order 升 ...
- Linux Input子系统浅析(二)-- 模拟tp上报键值【转】
转自:https://blog.csdn.net/xiaopangzi313/article/details/52383226 版权声明:本文为博主原创文章,未经博主允许不得转载. https://b ...
- PHP二维数组按照键值排序
在开发过程中,我们常常需要对二维数组按照数组的某个键来排序,这里提供两个封装好的方法,可以放到公共函数模块里以后需要的时候直接调用即可. /** * 二维数组按照键值降序排序 * @param arr ...
随机推荐
- vue调试方法
vue调试方法有如下三种 1.chrome谷歌插件vue-devtools 2.console.log().console.error().alert().debugger 3.设置全局变量,分为两种 ...
- OAuth2.0 协议的理解
OAuth(Open Authorization)协议就是为用户资源的授权提供了一个安全.开放.简易的标准. OAuth在第三方应用与服务提供商之间设置了一个授权层,第三方应用通过授权层获取令牌,再通 ...
- flask debug 模式开启
debug 模式开启 最近在写python flask 的Waf后台管理界面,想要启用调试模式,发现安装目前网上流行的两种方式均无法在我的Pycharm中打开调试模式. )直接在对象上设置 app.d ...
- Quartz.Net进阶之五:TriggerListener 、JobListener 和 SchedulerListener
一.介绍 今天开始学习监听器,就是 Listener,在Quartz.Net 中,主要包含3类的监听器,主要内容包括:TriggerListener .JobListener 和 SchedulerL ...
- Saliency Detection: A Spectral Residual Approach
Saliency Detection: A Spectral Residual Approach 题目:Saliency Detection: A Spectral Residual Approach ...
- Python用HTMLTestRunner生成html测试报告
小编的主机:mac 一.引入HTMLTestRunner包 1.下载HTMLTestRunner.py,已上传到网盘,点击下载 2.将HTMLTestRunner.py复制到python安装目录的Li ...
- 一、PyQt5基础概念与安装配置
一.初识PyQt5 对于桌面程序开发,用户图形界面(GUI)的设计非常重要.一款美观.易用的用户界面可以很大程度上提高对使用这的友好度.由于Python最初是作为脚本语言开发,并没有GUI功能.但Py ...
- Navicat Premium 修改MySQL密码(忘记密码的情况下)
Navicat Premium 修改MySQL密码 1,首先,Navicat Premium还能够连接MySQL. 2,选择数据库,右键单击,选择“命令行模式...”,下图示例 3,打开命令行模式, ...
- amazeui分页
<link rel="stylesheet" href="../../static/css/manage/amazeui.min.css" /> & ...
- springBoot 使用拦截器 入坑
近期使用SpringBoot 其中用到了拦截器 结果我的静态资源被全部拦截了,让我导致了好久才搞好: 看下图项目结构: 问题描述:上图划红框的资源都被拦截器给拦截了,搞得项目中不能访问:解决问题就是在 ...