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的更多相关文章

  1. es6初级之解构----之二 及 键值反转实现

    1.解构: 不定参数,扩展表达式 let arr = [100, 201, 303, 911]; let [one, ...others] = arr; console.log(others.leng ...

  2. YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能

    YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. 一.动态属性扩展 在实际的开发过程中,你肯定会遇到数 ...

  3. Show 一下最新的动态属性扩展功能与键值生成器功能

    Show 一下最新的动态属性扩展功能与键值生成器功能 YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. ...

  4. Android : 输入设备键值从底层到应用层的映射流程

    一.Android输入子系统简介: Android输入事件的源头是位于/dev/input/下的设备节点,而输入系统的终点是由WMS管理的某个窗口.最初的输入事件为内核生成的原始事件,而最终交付给窗口 ...

  5. php 二位数组按某个键值排序

    $arr=[ array( 'name'=>'小坏龙', 'age'=>28 ), array( 'name'=>'小坏龙2', 'age'=>14 ), array( 'na ...

  6. 【原】Learning Spark (Python版) 学习笔记(二)----键值对、数据读取与保存、共享特性

    本来应该上周更新的,结果碰上五一,懒癌发作,就推迟了 = =.以后还是要按时完成任务.废话不多说,第四章-第六章主要讲了三个内容:键值对.数据读取与保存与Spark的两个共享特性(累加器和广播变量). ...

  7. PHP 按二维数组的键值排序

    /** * 按二维数组的键值排序 * @param unknown $array 二维数组 * @param unknown $key 二维数组的键值 * @param string $order 升 ...

  8. Linux Input子系统浅析(二)-- 模拟tp上报键值【转】

    转自:https://blog.csdn.net/xiaopangzi313/article/details/52383226 版权声明:本文为博主原创文章,未经博主允许不得转载. https://b ...

  9. PHP二维数组按照键值排序

    在开发过程中,我们常常需要对二维数组按照数组的某个键来排序,这里提供两个封装好的方法,可以放到公共函数模块里以后需要的时候直接调用即可. /** * 二维数组按照键值降序排序 * @param arr ...

随机推荐

  1. Left Join B表,只取B表一条记录

    --用OUTER APPLY select b.* FROM a表 a OUTER APPLY () * from b表 WHERE [Name] = a.[AName] ORDER BY BNo d ...

  2. 关于js中操作数组的一些方法

    网上找的通篇看了一遍讲的很透收藏了!  转自(https://www.cnblogs.com/blogs-8888/p/6518683.html) 1.锁定数组的长度(让数组的长度变成只读). 1 2 ...

  3. python json.dumps()函数输出json格式,使用ensure_ascii参数对中文输入的支持

    在python使用过程中,输入中文,不能正常的输出,可以使用ensure_ascii参数来解决不能输入中文的问题 代码块: import json friends={"name": ...

  4. python脚本执行报错整理

    people = [ {'name':'alex','age':1000}, {'name':'wuxie','age':100}, {'name':'wangcanghai','age':9000} ...

  5. Codeforces 1086D Rock-Paper-Scissors Champion

    Description \(N\) 个人排成一排, 每个人都事先决定出剪刀.石头.布. 每次可以任意选两个相邻的人进行决斗. 规则和游戏一样. 但是如果平局, 则掷硬币来决定胜负. 输的人下场. 现要 ...

  6. Mac使用笔记大全

    1.mac中,怎么直接在当前文件夹打开终端? 步骤:(1)在键盘-快捷键-服务,勾选“新建位于文件夹位置的终端窗口”.(2)然后在需要打开终端的文件夹上,右键,“新建位于文件夹位置的终端窗口”即可. ...

  7. Finance财务软件帮助

    我们在凭证录入的时候可以使用这些快捷键增加效率: 单元格 快捷键 功能 摘要 F7 弹出摘要选择框 科目 F7 弹出科目选择框 科目 F8 弹出扩展字段编辑界面 金额 Esc/小键盘- 清空 金额 = ...

  8. java中的 java.util.concurrent.locks.ReentrantLock类中的lockInterruptibly()方法介绍

    在java的 java.util.concurrent.locks包中,ReentrantLock类实现了lock接口,lock接口用于加锁和解锁限制,加锁后必须释放锁,其他的线程才能进入到里面执行, ...

  9. 微信小程序开发之搞懂flex布局4——Main Axis

    Main Axis——主轴 当flex-direction设置为row或row-reverse时,主轴的方向为水平方向.此时flex item为行内级元素. 当flex-direction设置为col ...

  10. TJOI2010中位数

    中位数 上面是题目链接. 这一题比较水. 思路非常显然. 用mid查询时,只要返回中间值就行了. 主要就是add操作. 我们肯定不能插在末尾,然后用系统快排,这样只有30分. 那么正确的操作应该是二分 ...