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)

SparseArray具体解释,我说SparseArray,你说要!

Android SparseArray源码分析