源码阅读系列——基础篇(Android SparseArray 集合源码分析)

之前我们了解了很多JAVA的集合源码分析,我们继续开坑学习Android集合源码实现

1、构造函数

public class SparseArray<E> implements Cloneable {
    private int[] mKeys;  // 存放key的数组
    private Object[] mValues;  // 存放value的数组
    private int mSize;
    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;
    }
}

从这个成员变量,其实就能看出来SparseArray没有类似HashMap那种链表/树的结构,他存的就是value的object本身,key就是int本身,并且一个key对应一个value,key的数组mKeys和value的数组mValues长度理论上也是一致的。而且从命名也看出来,他没有转换hash值,所以也不会存在HashMap那种key碰撞的问题。

2、插入

public void put(int key, E value) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key); // 在当前已存入数据的key的数组mKeys中,按照二分查找,看这个key在mKeys中存不存在

        if (i >= 0) {
            mValues[i] = value;  // 如果存在这个key,覆盖这个key对应的value的值
        } else {
            // 如果没找到对应的key,返回的就是mKeys中最接近小于key的index(mKeys的索引),并取非。所以再取非一次,就得到原来的index了(也就是这里的i变量)
            i = ~i;

            // 如果返回的index,没有对应的value值,直接把这个key插在这个位置
            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }

            // 如果当前的mSize大于等于mKeys的长度 (代码一)
            if (mGarbage && mSize >= mKeys.length) {
                // 回收元素,实际是整理元素,我们后面会讲**加粗样式**
                gc();

                // 重新搜索一次
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }

            // 将value 和 key都插入到mKeys和mValue的数组中,这里涉及到一个扩容逻辑
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

这里有个疑问就是代码一为什么会存在mSize不等于mKeys的长度,mGarbage和gc有什么用,后面删除元素,我们再看
看一下扩容逻辑

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

        // 没有超过数组的最大长度,则正常插入
        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
       
        // 超过数组的最大长度,growsize扩容
        boolean[] newArray = new boolean[growSize(currentSize)];
        // 存入元素
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }

简单看一下扩容的实现

public static int growSize(int currentSize) {
    return currentSize <= 4 ? 8 : currentSize * 2;
}

也就是说,如果大小<=4 就直接设置为8,否则翻倍扩容。

3、删除

public void delete(int key) {
   // 二分法查找对应的key
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        if (mValues[i] != DELETED) {
           // 找到之后,将对应的index的value设置为DELETED,并且致标志位mGarbage
            mValues[i] = DELETED;
            mGarbage = true;
        }
    }
}

可以看到删除的时候,并没有真的在mKeys中移除i,也没有做mValue中的移除操作,因为这样势必会对数组再次进行遍历的挪移操作,很显然效率很低,所以插入时就能利用DELETED的标志位进行替换新的key和value的值。但是也存在两个问题:
1、key的值会变得相对分散,二分查找的效率不高,比如我本来实际上就只有10个元素,由于delete元素过多,查找的数组长度会变大
2、由于delete的元素过多,数组膨胀本身会造成很多的脏数据,对数组本身利用率也不高。
所以在insert的时候,我们看到存在mSize >= mKeys.length。所以同时满足mGarbage = true和mSize >= mKeys.length,我们对数组进行一次整理

private void gc() {
        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;
    }

这个过程很简单,也就是找到所有非DELETED的元素,并且依次加到数组中,这样数组就有离散的分布,变成了集中的排序到一起。mSize也就此时才能真正表示SparseArray中元素的真实个数。这也就是为什么我们获取size()的时候,调用方法这样实现:

public int size() {
    if (mGarbage) {
        gc();
    }

    return mSize;
}

在gc()操作的最后,也需要设置回标志位mGarbage为false,表示当前已经完成元素整理了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值