Android 开发也要懂得数据结构 - SparseArray源码

Posted 进击的包籽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 开发也要懂得数据结构 - SparseArray源码相关的知识,希望对你有一定的参考价值。

文章目录

  • 在之前分析 HashMap 就知道当容量达到 75% 时就需要扩容,那也就意味着 25% 的内存空间啥也不放,浪费掉了,为了解决这个问题,就有了 SparseArray
  • 本文章使用的是 JDK1.8 ,不同版本源码有差异。
  • 可先食用 Android 开发也要懂得数据结构 - HashMap源码

1.SparseArray特点

  • SparseArray的结构是 双数组 ,就是key和value都是数组,下标一一对应。
  • SparseArray虽然是 key-valye 结构,但是key只能是 int 类型,用于代替 HashMap<Integer,Object>,这也是缺点,还有 LongSparseArray
  • HashMap 处理 int 类型的key,是需要 装箱成Integer类型 ,消耗一些资源,而 SparseArray 就不需要装箱操作,更快一些。
  • HashMap 保存数据是以 Entry对象保存,还要计算hashCode,在内存方面是要大于 SparseArray。
  • SparseArray 的查找速度比较快,利用的是二分法查找,二分查找的要求就是key是有序排列的。
  • 二分查找虽然挺快的,数据量大的时候跟HashMap比就没有什么优势了,千级以下使用。

2.SparseArray常用的方法

2.1 基本参数

  • DELETED 是删除的位置放的东西,SparseArray 删除相当于是打上标记,就不需要移动数组,减少数组移动的耗时。
  • mGarbage 标志是如果有删除,就为true,用于后面的 gc() 方法的标记。
  • 可以看到 SparseArray 的 key 和 value 都是数组。
  • 还有一个长度mSize,与List,Map一样样,这个是实际数据的长度,不是容量的大小。
    private static final Object DELETED = new Object();
    private boolean mGarbage = false;
    
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
    private int[] mKeys;
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, E)
    private Object[] mValues;
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
    private int mSize;

2.2 构造方法

  • 构造方法两种,可以选填容量大小或者不填,不填容量,容量默认就为10。
  • 如果填写的容量为0,那就会创建一个非常轻量的数组。
    /**
     * Creates a new SparseArray containing no mappings.
     */
    public SparseArray() 
        this(10);
    

    /**
     * Creates a new SparseArray containing no mappings that will not
     * require any additional memory allocation to store the specified
     * number of mappings.  If you supply an initial capacity of 0, the
     * sparse array will be initialized with a light-weight representation
     * not requiring any additional array allocations.
     */
    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。
        mSize = 0;
    

2.3 二分查找 ContainerHelpers.binarySearch(mKeys, mSize, key)

  • 显示利用二分法,找出要放入元素的 key 对应的下标,如果找不到就返回二分范围小值的取反。
    //比较简单的二分法查找
    // 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) 
                //检索的范围最小就为中间+1
                lo = mid + 1;
             else if (midVal > value) 
                //如果大于,范围的最大就为中间-1
                hi = mid - 1;
             else 
                //找到就返回下标位置
                return mid;  // value found
            
        
        //找不到就返回lo取反
        return ~lo;  // value not present
    

2.4 放入元素 put(int key, E value)

  • 如果 key 位置已存在,直接覆盖。
  • 如果找不到 key 对应下标,且在范围内,有删除过闲置的位置,就把当前数据放在这个位置。
  • 如果这都不满足条件,就调用 gc() 方法整理一遍数据,把标记过删除的位置,干掉,再插入数据。
  • 插入元素如果容量不够,就扩容,如果原始容量小于4的,扩容成8,否则就以2倍的大小扩容。
  • 数组的插入需要移动后面位置的元素。
    /**
     * 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);

        if (i >= 0) 
            mValues[i] = value;
         else 
            //取反
            i = ~i;

			//如果i小于长度,且i位置没东西,就放在i位置。
            if (i < mSize && mValues[i] == DELETED) 
                mKeys[i] = key;
                mValues[i] = value;
                return;
            

			//如果有垃圾(删除过东西),且数据容量长度大于等于key数组时
            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++;
        
    

	//GrowingArrayUtils.insert
    /**
     * Primitive int version of @link #insert(Object[], int, int, Object).
     */
    public static int[] insert(int[] array, int currentSize, int index, int element) 
        assert currentSize <= array.length;

        if (currentSize + 1 <= array.length) 
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        

        int[] newArray = new int[growSize(currentSize)];
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    

	//GrowingArrayUtils.growSize
    //扩容,如果容量小于4的,扩容成8,否则就以2倍的大小扩容。
    /**
     * Given the current size of an array, returns an ideal size to which the array should grow.
     * This is typically double the given size, but should not be relied upon to do so in the
     * future.
     */
    public static int growSize(int currentSize) 
        return currentSize <= 4 ? 8 : currentSize * 2;
    

2.5 整理数据 gc()

  • 把非 DELETED 位置的数据,一个个往前移动。
  • mGarbage 设置为 false
    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);
    

2.6 删除元素 remove(int key)或者delete(int key)

  • 我们知道真正数组的删除,是要以移动后面的元素的,每次会造成大量的操作,所以改为标记清除,先打上标记,可以在放入元素时重新利用上空闲的位置,或者后面gc时再一次性清除掉。
    /**
     * Alias for @link #delete(int).
     */
    public void remove(int key) 
        delete(key);
    
    
    /**
     * 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;
            
        
    

2.7 查找元素 get(int key)

  • 利用二分查找找出 key 对应的下标,然后返回同样下标位置的值。
  • 两个参数的方法,也可设置找不到放回的默认值,如果找不到就返回默认值,否则是null。
    /**
     * Gets the Object mapped from the specified key, or <code>null</code>
     * if no such mapping has been made.
     */
    public E get(int key) 
        return get(key, null);
    
    
    /**
     * Gets the Object mapped from the specified key, or the specified Object
     * if no such mapping has been made.
     */
    @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];
        
    

2.8 查找key对应的下标 indexOfKey(int key)

  • 如果有标记垃圾,先整理一遍再放回 key 对应的下标。
    /**
     * Returns the index for which @link #keyAt would return the
     * specified key, or a negative number if the specified
     * key is not mapped.
     */
    public int indexOfKey(int key) 
        if (mGarbage) 
            gc();
        

        return ContainerHelpers.binarySearch(mKeys, mSize, key);
    

2.9 查找value对应的下标 indexOfValue(E value)

  • 查找之前依然会整理一次数据,不同位置都可能保存着这个value,所以遍历后,返回第一个,如果找不到就返回-1。
    /**
     * Returns an index for which @link #valueAt would return the
     * specified value, or a negative number if no keys map to the
     * specified value.
     * <p>Beware that this is a linear search, unlike lookups by key,
     * and that multiple keys can map to the same value and this will
     * find only one of them.
     * <p>Note also that unlike most collections' @code indexOf methods,
     * this method compares values using @code == rather than @code equals.
     */
    public int indexOfValue(E value) 
        if (mGarbage) 
            gc();
        

        for (int i = 0; i < mSize; i++) 
            if (mValues[i] == value) 
                return i;
            
        

        return -1;
    

2.10 长度size()

  • 返回数据的长度。
    /**
     * Returns the number of key-value mappings that this SparseArray
     * currently stores.
     */
    public int size() 
        if (mGarbage) 
            gc();
        

        return mSize;
    

以上是关于Android 开发也要懂得数据结构 - SparseArray源码的主要内容,如果未能解决你的问题,请参考以下文章

Android 开发也要懂得数据结构 - ArrayList源码

Android 开发也要懂得数据结构 - LinkList源码

学PHP也要懂得HTML

深入懂得android view 生命周期

Android 开发也要掌握的Java知识 -ThreadLocal

Android 面试之必问性能优化