Android集合SparseArray的使用及源码解析

SparseArray使用及源码分析

    • 使用方法
    • 原理分析
      • 1.构造方法
      • 2.其他方法
        • 2.1 append方法
        • 2.2 put方法
        • 2.3 ContainerHelpers.binarySearch方法(二分查找)
        • 2.4 get方法
        • 2.5 gc方法
    • 优缺点和应用场景
      • 1.优点
      • 2.缺点
      • 3.应用场景

使用方法

SparseArray源码来自:android-28 android.util.SparseArray
首先看一下SparseArray的基本使用方法:

        /**
         * 创建对象
         */
        SparseArray sparseArray = new SparseArray<>();

        /**
         * 添加元素
         */
        sparseArray.append(0, "str1");
        sparseArray.put(1, "str2");

        /**
         * 删除元素,两种方式等同
         */
        sparseArray.remove(1);
        sparseArray.delete(1);

        /**
         * 修改元素,put或者append相同的key值即可
         */
        sparseArray.put(1, "str3");
        sparseArray.append(1, "str4");

        /**
         * 查找,遍历
         */
        //方式1
        for (int i = 0; i < sparseArray.size(); i++) {
            Log.i(TAG, sparseArray.valueAt(i));
        }
        //方式2
        for (int i = 0; i < sparseArray.size(); i++) {
            int key = sparseArray.keyAt(i);
            Log.i(TAG, sparseArray.get(key));
        }

SparseArray和HashMap有点相似,唯一不同的就是key和value的类型,HashMap的key值和value值为泛型,但是SparseArray 的key值只能为int 类型,value值为Object类型。

原理分析

    private static final Object DELETED = new Object();
    private boolean mGarbage = false;

    private int[] mKeys;
    private Object[] mValues;
    private int mSize;

这里先解释一下这几个变量:
1.DELETED是一个标志字段,用于判断是否删除
2.mGarbage也是一个标志字段,用于确定当前是否需要垃圾回收
3.mKeys数组用于存储key
4.mValues数组用于存储值
5.mSize表示当前SparseArray有几个元素

接下来看几个重要方法:

1.构造方法

    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;
    }

SparseArray构造方法中,创建了两个数组mKeys、mValues分别存放int与Object,其默认长度为10。

2.其他方法

2.1 append方法

    /**
     * Puts a key/value pair into the array, optimizing for the case where
     * the key is greater than all existing keys in the array.
     */
    public void append(int key, E value) {
        if (mSize != 0 && key <= mKeys[mSize - 1]) {
        	//当mSize不为0并且不大于mKeys数组中的最大值时,因为mKeys是一个升序数组,最大值即为mKeys[mSize-1]
            //直接执行put方法,否则继续向下执行
            put(key, value);
            return;
        }
        
		//当垃圾回收标志mGarbage为true并且当前元素已经占满整个数组,执行gc进行空间压缩
        if (mGarbage && mSize >= mKeys.length) {
            gc();
        }
        
		//当数组为空,或者key值大于当前mKeys数组最大值的时候,在数组最后一个位置插入元素
        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
        mValues = GrowingArrayUtils.append(mValues, mSize, value);
        //元素加一
        mSize++;
    }

2.2 put方法

    public void put(int key, E value) {
		// 二分查找,key在mKeys列表中对应的index
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
		// 如果找到,则直接赋值
        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;// binarySearch方法中,找不到时,i取了其非,这里再次取非,则非非则正
			// 如果该位置的数据正好被删除,则赋值
            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }
			// 如果有数据被删除了,则gc
            if (mGarbage && mSize >= mKeys.length) {
                gc();

                // Search again because indices may have changed.
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }
			// 插入数据,增长mKeys与mValues列表
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

因为key为int,不存在hash冲突;mKeys为有序列表,通过二分查找,找到要插入的key对应的index ,相对于查找hash表应该算是费时间,但节省了内存,所以是时间换取空间;通过二分查找到的index,将Value插入到mValues数组的对应位置。

2.3 ContainerHelpers.binarySearch方法(二分查找)

    // This is Arrays.binarySearch(), but doesn't do any argument validation.
    static int binarySearch(int[] array, int size, int value) {
        int lo = 0;
        int hi = size - 1;
		// 循环查找
        while (lo <= hi) {
        	// 取中间位置元素
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];

            if (midVal < value) {// 如果中间元素小于要查找元素,则midIndex赋值给 lo
                lo = mid + 1;
            } else if (midVal > value) {// 如果中间元素大于要查找元素,则midIndex赋值给 hi 
                hi = mid - 1;
            } else {// 找到则返回
                return mid;  // value found
            }
        }
        // 找不到,则lo 取非
        return ~lo;  // value not present
    }

2.4 get方法

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

    @SuppressWarnings("unchecked")
    public E get(int key, E valueIfKeyNotFound) {
        // mKeys数组中采用二分查找,找到key对应的index
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
        // 没有找到,则返回空
        if (i < 0 || mValues[i] == DELETED) {
            return valueIfKeyNotFound;
        } else {// 找到则返回对应的value
            return (E) mValues[i];
        }
    }

每次调用get方法,则需执行一次mKeys数组的二分查找,因此mKeys数组越大则二分查找的时间就越长,因此SparseArray在大量数据,千以上时,效率较低。

2.5 gc方法

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

        int n = mSize;
        int o = 0;
        int[] keys = mKeys;//保存新的key值的数组
        Object[] values = mValues;//保存新的value值的数组

        for (int i = 0; i < n; i++) {
            Object val = values[i];

            if (val != DELETED) {//如果该value值不为DELETED,也就是不删除
                if (i != o) {
                    keys[o] = keys[i];//将 i 位置的元素向前移动到 o 处,这样做最终会让所有的非DELETED元素连续紧挨在数组前面
                    values[o] = val;
                    values[i] = null;//释放空间
                }

                o++;//新数组元素加一
            }
        }

        mGarbage = false;//回收完毕,置为false
        mSize = o;//回收之后数组的大小

        // Log.e("SparseArray", "gc end with " + mSize);
    }

主要就是通过之前设置的DELETED 标签来判断是否需要删除,然后进行数组前移操作,将不需要删除的元素排在一起,最后设置新的数组大小和设置mGarbage 为false。

优缺点和应用场景

1.优点

1.1 避免了基本数据类型的装箱操作
1.2 会定期通过gc函数来清理内存,内存利用率高
1.3 数据量小的情况下,随机访问的效率更高

2.缺点

2.1 数据量巨大时,复制数组成本巨大,gc()成本也巨大,查询效率也会明显下降
2.2 插入操作需要复制数组,增删效率降低

3.应用场景

3.1 数量 <1000的时候
3.2 存取的value为指定类型的,比如boolean(SparseBooleanArray)、int(SparseIntArray)、long(SparseLongArray)可以避免自动装箱和拆箱问题

你可能感兴趣的:(Android开发)