SparseArray关键源码

SparseArray是一种在Android中用于高效存储整数到对象映射的数据结构,它避免了自动装箱并减少了内存开销。文章详细介绍了SparseArray的内部实现,包括其与HashMap的区别、主要操作的时间复杂度、以及如何通过二分查找和优化的数组管理来提高性能。此外,还提到了与ArrayUtils和GrowingArrayUtils类的相关代码示例。
摘要由CSDN通过智能技术生成

一、类介绍
英文
SparseArray maps integers to Objects and, unlike a normal array of Objects, its indices can contain gaps. SparseArray is intended to be more memory-efficient than a HashMap, because it avoids auto-boxing keys and its data structure doesn't rely on an extra entry object for each mapping.
Note that this container keeps its mappings in an array data structure, using a binary search to find keys. The implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a HashMap because lookups require a binary search, and adds and removes require inserting and deleting entries in the array. For containers holding up to hundreds of items, the performance difference is less than 50%.
To help with performance, the container includes an optimization when removing keys: instead of compacting its array immediately, it leaves the removed entry marked as deleted. The entry can then be re-used for the same key or compacted later in a single garbage collection of all removed entries. This garbage collection must be performed whenever the array needs to be grown, or when the map size or entry values are retrieved.
It is possible to iterate over the items in this container using keyAt(int) and valueAt(int). Iterating over the keys using keyAt(int) with ascending values of the index returns the keys in ascending order. In the case of valueAt(int), the values corresponding to the keys are returned in ascending order.
 
SparseArray:稀疏数组,储存的数据可呈现松散不连续状态。类似map数据结构。包含数组int[] mKeys、Object[] mValues。数据较少时,比HashMap更省内存。
特点:
1、类似map数据结构,key为int类型,value为Object。
2、put/set、delete/remove、get都用二分查找,时间复杂度:O(log n)。
3、setValueAt(int index, E value)等操作角标的方法,时间复杂度:O(1)
4、it avoids auto-boxing keys and values and its data structure doesn't rely on an extra entry object for each mapping
 
二、关联类关联代码源码
1、com.android.internal.util.ArrayUtils
public class ArrayUtils {
    public static Object[] newUnpaddedObjectArray(int minLen) {
        return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen);
    }
}
 
 
2、com.android.internal.util.GrowingArrayUtils
/**
     * 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 = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }
 
 
/**
     * Primitive int version of {@link #append(Object[], int, Object)}.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public static int[] append(int[] array, int currentSize, int element) {
        assert currentSize <= array.length;
 
        if (currentSize + 1 > array.length) {
            int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, currentSize);
            array = newArray;
        }
        array[currentSize] = element;
        return array;
    }
 
 
/**
     * Appends an element to the end of the array, growing the array if there is no more room.
     * @param array The array to which to append the element. This must NOT be null.
     * @param currentSize The number of elements in the array. Must be less than or equal to
     *                    array.length.
     * @param element The element to append.
     * @return the array to which the element was appended. This may be different than the given
     *         array.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public static <T> T[] append(T[] array, int currentSize, T element) {
        assert currentSize <= array.length;
 
        if (currentSize + 1 > array.length) {
            @SuppressWarnings("unchecked")
            T[] newArray = ArrayUtils.newUnpaddedArray(
                    (Class<T>) array.getClass().getComponentType(), growSize(currentSize));
            System.arraycopy(array, 0, newArray, 0, currentSize);
            array = newArray;
        }
        array[currentSize] = element;
        return array;
    }
 
 
/**
     * 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;
    }
 
 
 
三、SparseArray源码
 
1、构造方法及主要变量
public class SparseArray<E> implements Cloneable {
    // 标记值已被删除
    private static final Object DELETED = new Object();
    //标记是否有元素被移除
    private boolean mGarbage = false;
 
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use keyAt(int)
    private int[] mKeys;// 存储key的集合,保存的key是有小到大排列的。
 
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use valueAt(int), setValueAt(int, E)
    private Object[] mValues;// 存储value的集合
 
    @UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
    private int mSize;// 存入的元素个数
 
    // 默认初始容量为10
    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;
    }
 
    @Override
    @SuppressWarnings("unchecked")
    public SparseArray<E> clone() {
        SparseArray<E> clone = null;
        try {
            clone = (SparseArray<E>) super.clone();
            clone.mKeys = mKeys.clone();
            clone.mValues = mValues.clone();
        } catch (CloneNotSupportedException cnse) {
            /* ignore */
        }
        return clone;
    }
}
 
 
2、remove()
public void remove(int key) {
    delete(key);
}
 
public void delete(int key) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
    if (i >= 0) {//大于等于表示存在key
        if (mValues[i] != DELETED) {
            mValues[i] = DELETED;//把key对应的value标记DELETED
            mGarbage = true;//true表示有元素被删除
        }
    }
}
remove(int key)直接调用了delete(int key),删除时,把value标记为DELETED,造成value的稀疏状态,这大概是SparseArray名字的由来吧。
 
3、gc():删除位置后的所有数据向前移动,o表示非DELETED元素的新角标,i-o的值表示移动量。
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;
}
 
多个方法中使用gc(),如:put(int key, E value)、append(int key, E value)、indexOfValue(E value)、indexOfKey(int key)、setValueAt(int index, E value)、valueAt(int index)、keyAt(int index)、size()等
 
4、put()
4.1、put(int key, E value)
public void put(int key, E value) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
 
    if (i >= 0) {//大于等于0,mKeys中存在key,直接更新key对应的值
        mValues[i] = value;
    } else {//小于0,mKeys中不存在key。需要在新位置插入对应的key-value
 
        // 取反得到待插入key的位置,与ContainerHelpers.binarySearch()有关
        i = ~i;
 
        // 插入位置小于mSize,并且该位置的value被标记为DELETED,则把key和value插入i位置
        if (i < mSize && mValues[i] == DELETED) {
            mKeys[i] = key;
            mValues[i] = value;
            return;
        }
        //存在被标记为DELETE的元素
        if (mGarbage && mSize >= mKeys.length) {
            //调用gc方法移除标记为DELETE的元素
            gc();
                    
            //由于gc方法移动了数组,因此插入位置可能有变化,所以需要重新计算插入位置
            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        }  
       //GrowingArrayUtils的insert方法将会将插入位置之后的所有数据向后移动一位,然后将key和value分别插入到mKeys和mValue对应的第i个位置,如果数组空间不足还会开启扩容
        mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
        mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
        mSize++;
    }
}
 
4.2、GrowingArrayUtils的insert(T[] array, int currentSize, int index, T element)
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
    assert currentSize <= array.length;
    //如果插入后数组size小于数组长度,能进行插入操作
    if (currentSize + 1 <= array.length) {
        //将index之后的所有元素向后移动一位
        System.arraycopy(array, index, array, index + 1, currentSize - index);
        //将key插入到index的位置
        array[index] = element;
        return array;
    }
 
    //数组已满,需要进行扩容操作。newArray即为扩容后的数组
    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);
    return newArray;
}
 
4.3、GrowingArrayUtils.growSize(int currentSize)
// 返回扩容后的size
public static int growSize(int currentSize) {
    return currentSize <= 4 ? 8 : currentSize * 2;
}
 
4.4、System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
src:源数组、srcPos:源数组要复制的起始位置、dest:目的数组、destPos:目的数组放置的起始位置、length:源数组复制的长度.
 
示例:
数组1:int[] arr = { 1, 2, 3, 4, 5 };
数组2:int[] arr2 = { 5, 6,7, 8, 9 };
运行:System.arraycopy(arr, 1, arr2, 0, 3);
得到:int[] arr2 = { 2, 3, 4, 8, 9 };
 
 
5、get()
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];
    }
}

参考文档

这一次,彻底搞懂SparseArray实现原理_sparsearray原理_我赌一包辣条的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值