SparseArray源码解读

SparseArray源码解读

google推荐使用SparseArray来代替HashMap,可见SparseArray应该有其优点,至于是什么优点,我们先来看看源码,就比较清楚了。

一、构造方法

SparseArray有两个构造方法,一个无参构造方法和一个有参构造方法,无参构造方法本质上还是调用了有参构造方法。

public SparseArray() {
        this(10);
    }
public SparseArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = EmptyArray.INT;
            mValues = EmptyArray.OBJECT;
        } else {
            mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
            mKeys = new int[mValues.length];
        }
        mSize = 0;
    }

由构造方法可以看出,构造方法的作用就是:1、new一个int类型的数组mKeys,注意是int类型,而不是Integer类型,该数组是用来存放key的;2、new一个Object类型的数组mValues,该数组是用来存放value的;3、初始化变量mSize,该变量是键值对的个数,而不是数组的长度,初始值为0.

二、删除:public void delete(long key)

public void delete(long key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }
static int binarySearch(long[] array, int size, long value) {
        int lo = 0;
        int hi = size - 1;

        while (lo <= hi) {
            final int mid = (lo + hi) >>> 1;
            final long midVal = array[mid];

            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid;  // value found
            }
        }
        return ~lo;  // value not present
    }

可见,删除的逻辑是通过二分查找法定位key在数组mKeys的索引,如果找到key所对应的的索引,那么把对应的value置位DELETED,DELETED是一个Object对象,同时把mGarbage变量置位true,可见,只要进行了删除操作,变量mGarbage的值就是true。有两点需要注意:

(1)这里对ContainerHelpers.binarySearch进行一个简要的说明,该方法如果找到了key,那么返回key所对应的的位置索引,即是一个大于等于0且小于mSize的一个数,如果最终没有找到key,那么返回的是最后查找的索引的按位取反,源码中是~lo,我们只需记得返回一个负数即可,并且用的时候,需要对返回值取反。

(2)mSize的值并没有变化,也就是delete方法并不会使键值对的逻辑数量减少。

三、插入:public void put(long key, E value)

public void put(long key, E value) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            mValues[i] = 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++;
        }
    }

其实put方法有两层含义,如果key值已有,其实就是更新value,否则是进行插入。put方法的逻辑也是先用二分查找算法确定key的索引,如果key值已存在,那么mValues[i] = value即可;如果key值不存在,我们先对ContainerHelpers.binarySearch的值进行取反,得到二分查找最后的索引位置,如果该索引对应的value是DELETED,那么更新该位置的key和value即可,这样不用对数组进行扩充,也不用对数组的元素进行移动;如果如果该索引对应的value不是DELETED,那么就需要进行插入了,在插入之前,如果mGarbage为true并且mSize不小于mKeys.length,那么要进行gc操作,该gc操作不是java虚拟机的垃圾回收的gc,而是SparseArray定义的一个方法,源码如下:

private void gc() {
        // Log.e("SparseArray", "gc start with " + mSize);

        int n = mSize;
        int o = 0;
        long[] 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方法是把value被标记为DELETED的元素给去掉了,并且有效数据前移,相当于去掉了内存碎片,如下图所示(仅供示意):

 

好了,我们只介绍put和delete方法,因为这两个方法是SparseArray内存管理的核心算法所在。通过上面的源码分析,我们可以总结得出:1、SparseArray使用的是两个数组来保存key和value,并且会在某些情况下采用元素拷贝挪移的方式清理内存碎片,提高已申请内存的使用效率,最大限度的节省内存开销;2、SparseArray的key都是基本数据类型,省去了装箱过程,这个也是一项优化。

 

SparseArray的相关变型

SparseIntArray:key是int类型,value是int类型

SparseBooleanArray:key是int类型,value是boolean类型

SparseLongArray:key是int类型,value是long类型

LongSparseArray:key是long类型,value是泛型,需要在实例化时指定。

 

关于SparseArray和HashMap的比较以及如何进行选择:

1、SparseArray比HashMap结构简洁,因此在内存使用方面SparseArray比HashMap有优势。

2、在插入、删除、查询效率方面,SparArray都涉及到了折半查找,效率为O(logN),不如HashMap。

3、SparseArray的key是int类型,省去了装箱过程,而HashMap的key是不支持基本数据类型的。

综上,个人给出建议如下:在小数据量时,HashMap和SparArray的时间效率相差不会很明显,这时候推荐采用SparseArray,可以节省内存;在数据量比较大时,数据的处理速度可能优先级比较高,特别是对于安卓客户端,用户的响应速度尤为重要,推荐使用HashMap。当然这些比较仅限于key的类型是int、boolean、long三种类型的,因为SparArray及其变型(如LongSparseArray、BooleanSparseArray)目前只支持这三种类型的key,至于其他类型,如String、Object等,只能选择HashMap。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android海纳百川

打赏加微信,送跑车加管理

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值