SparseArray原理和使用
Posted 碎格子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SparseArray原理和使用相关的知识,希望对你有一定的参考价值。
SparseArray是什么
我们有时候使用HashMap存放数据时,会存入<Integer,Object> 这类的键值对:
而在早前编译器会推荐使用SparseArray替代HashMap进行这类键值对的存取。(现在不知道为什么没有提示了)所以其实SparseArray和HashMap大同小异,都是存放键值对的数据结构,只不过SparseArray只用来存放key为简单类型int型的键值对。
为什么要用SparseArray?
SparseArray的注解(百度翻译):
SparseArray将整数映射到对象,与普通的对象数组不同,它的索引可以包含间隙。SparseArray旨在比HashMap更节省内存,因为它避免了自动装箱键,并且其数据结构不依赖于每个映射的额外entry对象。
注意,这个容器将其映射保存在数组数据结构中,使用二进制搜索来查找键。该实现不适用于可能包含大量项的数据结构。它通常比HashMap慢,因为查找需要二进制搜索,添加和删除需要在数组中插入和删除条目。对于容纳数百件物品的容器,性能差异小于50%。
为了提高性能,容器在删除键时包含了一个优化:它不会立即压缩数组,而是将删除的条目标记为已删除。然后,该条目可以重新用于同一个键,或者稍后在所有删除的条目的单个垃圾收集中进行压缩。每当阵列需要增长时,或者当检索映射大小或条目值时,都必须执行此垃圾收集。
可以使用keyAt(int)和valueAt(nt)迭代此容器中的项。使用keyAt(int)和索引的升序值对键进行迭代,以升序返回键。对于valueAt(int),将按升序返回与键对应的值。
SparseArray 因为key值存入的是基本数据类型int型的,而HashMap的key值是任意的包装类型,而且SparseArray不使用额外的结构体(Entry)对象,储存成本降低。
SparseArray操作方法
和HashMap一样,SparseArray也有put方法:
public void put(int key, E value)
//mKeys存入put进来的key的值
int i = ContainerHelpers.binarySearch(mKeys, mSize, key); //进行二分查找法查找key要插入的位置
if (i >= 0)
mValues[i] = value; //如果在数组中查找到下标,则将value重新赋值
else
i = ~i;
if (i < mSize && mValues[i] == DELETED) //这里有个伏笔
mKeys[i] = key;
mValues[i] = value;
return;
if (mGarbage && mSize >= mKeys.length)
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
我们知道,数组的增删时间复杂度都比较高,因为需要移动数组,而SparseArray在删除的时候做了一个特殊的处理,它并没有进行数组移动,而是给删除的数据位标记了一个DELETED,DELETED是我们创建SparseArray时创建的一个Object对象。
public void delete(int key)
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0)
if (mValues[i] != DELETED)
mValues[i] = DELETED; //如果查找到的数据没有被标记为DELETE
mGarbage = true;
val sparseArray = SparseArray<String>()
sparseArray.put(0, "aaa")
sparseArray.put(8, "bbb")
sparseArray.put(5, "ccc")
sparseArray.put(6, "dddd")
sparseArray.delete(6)
当我删除key为6的数据时,实际上key还存在,只不过会给value赋值为一个名为DELETE的Object
那这个时候问题来了,这个delete方法并没有真正的删除数据啊,那我们的SparseArray的长度会不会有问题?
带着这个问题,我们看下获取size的方法:
public int size()
if (mGarbage) //当出现删除操作的时候mGarbage会赋值为true
gc();
return mSize;
size里面代码很简单,调用了gc()方法,接下来再看下gc()方法做了什么:
private void gc()
// Log.e("SparseArray", "gc start with " + mSize);
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;
// Log.e("SparseArray", "gc end with " + mSize);
gc方法会剔除标记为DELETE的数据,并且将真正的数据重新放到数组中去。
而它的get方法也很简单:通过二分查找法直接找到key所在的下标,然后取出值
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];
gc在什么时候使用?
每次要获取真正数量的时候,就会调用gc,比如获取下标indexOfKey、indexOfValue、indexOfValueByValue以及获取size方法。
通过DELETED标记有什么好处?
有一个场景,比如我删除完key为6的数据后,我又想往里put数据,如果没有标记法,会发生什么?首先删除数组里的数据会导致数组移动,而put数据又会进行数组移动。而使用标记法,只需要找到key所在下标的value数据,并且将对应数据填入即可,不会导致数组移动。
缺点:
SparseArray不适合用来存放大量数据,下面的例子可以看出,当数据一大了之后,二分查找的效率是低于红黑树的,所以当要存放量大的数据时,SparseArray的效率是不如HashMap。
以上是关于SparseArray原理和使用的主要内容,如果未能解决你的问题,请参考以下文章
Android Gems — Java源码分析之HashMap和SparseArray
Android Gems — Java源码分析之HashMap和SparseArray
如何使用包含 Hashmap 和 SparseArray 的自定义类实现 parcelable?
Android内存优化(使用SparseArray和ArrayMap代替HashMap)