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。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值