SparseArray源码分析

SparseArray源码分析

在SparseArray中key数组和value数组中,它们的下标是一一对应的。

1.构造函数

可以看出SparseArray中的key和value分别存在不在不同的数组中,并且初始容量为10。

private int[] mKeys;//key数组
private Object[] mValues;//value数组
private int mSize;//大小
private static final Object DELETED = new Object();//value数组的标记值

/**
 * Creates a new SparseArray containing no mappings.
 * sparse 稀疏的
 */
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;
}
2.put

SparseArray的size即使SparseArray的大小,也是key数组和value数组已经存入数据的个数。

通过二分查找找到出入的key在key数组中对应的下标

1、下标不为负,说明key数组中已经存在传入的key,就将value数组中下标对应的数据替换为传入的value,size不变。

2、下标为负,说明key数组在没有对应的key,对二分查找返回值进行取反得到key应该插入的下标,

  • ​ 如果下标小于SparseArray的大小mSize(不是其中数组的长度),并且value数组中该下标处的数据为DELETED,表示该位置的数据已被删除或没有存入过数据,就将key和value分别存入key数组和value数组中对应下标的位置。
  • 如果下标出value数组的数据不为DELETED,表示该下标处已经有数据,会分为两种情况,
    1. 在插入一个数据size仍小于等于数组长度currentSize + 1 <= array.length,从下标处开始的数据所有数据向后移动以为,然后将传入的数据存到下标处。
    2. 在插入一个数据size将大于数组长度,将对数组进行扩容,扩容的大小为原大小的两倍,并原数组中的数据复制到新数组,下标前的的数据直接复制,下标处及其后面的数据后移一位复制,数据存到下标处。
    3. 插入数据前如果有删除操作,并且SparseArray的大小已经大于等于数组的长度,将对数组进行整理,去除数组中的碎片,得到真实的size。
    /**
     * Adds a mapping from the specified key to the specified value,
     * replacing the previous mapping from the specified key if there
     * was one.
     */
    public void put(int key, E value) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);//二分查找,找到key数组中key的下标

        if (i >= 0) {//key数组中包含有key
            mValues[i] = value;//将value数组中对应位置的值替换为put进来的值
        } else {//key数组中找不到对应的key
            i = ~i;//binarySearch返回的值,刚好是key可以插入位置取反

            if (i < mSize && mValues[i] == DELETED) {//value数组中该位置的数据已被删除或没有存入过数据
                mKeys[i] = key;//将key存入key数组中对应位置
                mValues[i] = value;//value也存入到value数组中与key下标相同的位置
                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++;
        }
    }

    public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
        assert currentSize <= array.length;

        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);//从index开始将数组后的数据后移一位
            array[index] = element;//将数据插入移出的位置
            return array;
        }
		//currentSize + 1>array.length,说明再加入一个数,原数组就存不下了,需要对数组进行扩容,也就是创建容量更大的数组
        @SuppressWarnings("unchecked")
        T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
                growSize(currentSize));
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);//将原数组的数据copy到新数组
        return newArray;
    }

3.gc

对数组中的碎片进行整理,重新计算size。

    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);
    }
4.remove\delete

通过key找到下标,再通过下标将value数组中对应位置的数据设为DELETE,并将mGarbage置为true。

在delete方法中我们没有看到size-1的操作,这时mGarbage=true就很关键,size()、put()中都能看到。

    /**
     * Removes the mapping from the specified key, if there was any.
     */
    public void delete(int key) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            if (mValues[i] != DELETED) {
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }

    /**
     * Alias for {@link #delete(int)}.
     */
    public void remove(int key) {
        delete(key);
    }
5.size

在获取SparseArray的大小前,如果删除过数据,都会调用gc()对SparseArray中的数组进行整理。

    /**
     * Returns the number of key-value mappings that this SparseArray
     * currently stores.
     */
    public int size() {
        if (mGarbage) {
            gc();
        }

        return mSize;
    }
6.get

通过key找到下标,再通过下标将value数组中对应位置的数据返回。

    public E get(int key) {
        return get(key, null);
    }

    @SuppressWarnings("unchecked")
    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];
        }
    }
7.性能分析
  1. 在删除数据后,数组没有进行前移,只是将数据置为Object对象,所以速度很快。
  2. 插入数据,在通过key找到的下标对应的value为Object直接对数组中的数据进行替换,不用进行数组的后移,提升了插入效率。
  3. 查找数据时,在key数组中通过二分查找可以很快找到下标,从而通过下标确定value,不用像HashMap对链表进行轮询。
  4. 相对于HashMap的缺点:SparseArray的key值只能是int类型,而HashMap的key可以是任意类型。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值