Android SparseArray 源码解析

来源:互联网 发布:淘宝代购耐克是正品吗 编辑:程序博客网 时间:2024/06/06 13:24

前段时间内推百度,二面时候被问到Android里的sparseArray里的细节问题,当时答的并不好,所以看下源码,专门做一个解析。
先贴源码

package android.util;import com.android.internal.util.ArrayUtils;import com.android.internal.util.GrowingArrayUtils;import libcore.util.EmptyArray;public class SparseArray<E> implements Cloneable {    private static final Object DELETED = new Object();    private boolean mGarbage = false;    private int[] mKeys;    private Object[] mValues;    private int mSize;//当前实际存放的对象数量    /**     * Creates a new SparseArray containing no mappings.     */    public SparseArray() {        this(10);//默认初始化大小为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) {//如果指定的初始值为0,则获取两个长度为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;    }    /**     * 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];        }    }    /**     * 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;            }        }    }    /**     * @hide     * Removes the mapping from the specified key, if there was any, returning the old value.     */    public E removeReturnOld(int key) {        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);        if (i >= 0) {            if (mValues[i] != DELETED) {                final E old = (E) mValues[i];                mValues[i] = DELETED;                mGarbage = true;                return old;            }        }        return null;    }    /**     * Alias for {@link #delete(int)}.     */    public void remove(int key) {        delete(key);    }    /**     * Removes the mapping at the specified index.     */    public void removeAt(int index) {        if (mValues[index] != DELETED) {            mValues[index] = DELETED;            mGarbage = true;        }    }    /**     * Remove a range of mappings as a batch.     *     * @param index Index to begin at     * @param size Number of mappings to remove     */    public void removeAtRange(int index, int size) {        final int end = Math.min(mSize, index + size);        for (int i = index; i < end; i++) {            removeAt(i);        }    }    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);    }    /**     * 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所在索引        if (i >= 0) {            mValues[i] = value;        } else {            i = ~i;            if (i < mSize && mValues[i] == DELETED) {                mKeys[i] = key;                mValues[i] = value;                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++;        }    }    /**     * Returns the number of key-value mappings that this SparseArray     * currently stores.     */    public int size() {        if (mGarbage) {            gc();        }        return mSize;    }    /**     * Given an index in the range <code>0...size()-1</code>, returns     * the key from the <code>index</code>th key-value mapping that this     * SparseArray stores.     *     * <p>The keys corresponding to indices in ascending order are guaranteed to     * be in ascending order, e.g., <code>keyAt(0)</code> will return the     * smallest key and <code>keyAt(size()-1)</code> will return the largest     * key.</p>     */    public int keyAt(int index) {        if (mGarbage) {            gc();        }        return mKeys[index];    }    /**     * Given an index in the range <code>0...size()-1</code>, returns     * the value from the <code>index</code>th key-value mapping that this     * SparseArray stores.     *     * <p>The values corresponding to indices in ascending order are guaranteed     * to be associated with keys in ascending order, e.g.,     * <code>valueAt(0)</code> will return the value associated with the     * smallest key and <code>valueAt(size()-1)</code> will return the value     * associated with the largest key.</p>     */    @SuppressWarnings("unchecked")    public E valueAt(int index) {        if (mGarbage) {            gc();        }        return (E) mValues[index];    }    /**     * Given an index in the range <code>0...size()-1</code>, sets a new     * value for the <code>index</code>th key-value mapping that this     * SparseArray stores.     */    public void setValueAt(int index, E value) {        if (mGarbage) {            gc();        }        mValues[index] = value;    }    /**     * 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);    }    /**     * Returns an index for which {@link #valueAt} would return the     * specified key, 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;    }    /**     * Removes all key-value mappings from this SparseArray.     */    public void clear() {        int n = mSize;        Object[] values = mValues;        for (int i = 0; i < n; i++) {            values[i] = null;        }        mSize = 0;        mGarbage = false;    }    /**     * 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]) {            put(key, value);            return;        }        if (mGarbage && mSize >= mKeys.length) {            gc();        }        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);        mValues = GrowingArrayUtils.append(mValues, mSize, value);        mSize++;    }    /**     * {@inheritDoc}     *     * <p>This implementation composes a string by iterating over its mappings. If     * this map contains itself as a value, the string "(this Map)"     * will appear in its place.     */    @Override    public String toString() {        if (size() <= 0) {            return "{}";        }        StringBuilder buffer = new StringBuilder(mSize * 28);        buffer.append('{');        for (int i=0; i<mSize; i++) {            if (i > 0) {                buffer.append(", ");            }            int key = keyAt(i);            buffer.append(key);            buffer.append('=');            Object value = valueAt(i);            if (value != this) {                buffer.append(value);            } else {                buffer.append("(this Map)");            }        }        buffer.append('}');        return buffer.toString();    }}

接下来开始一步一步解读。
打开源码我们先看到类的描述是

* SparseArrays map integers to Objects.  Unlike a normal array of Objects, * there can be gaps in the indices.  It is intended to be more memory efficient * than using a HashMap to map Integers to Objects, both because it avoids * auto-boxing keys and its data structure doesn't rely on an extra entry object * for each mapping. *

翻译过来的意思是sparseArray是映射整数到对象,比使用hashmap映射整数到对象更高效,因为它避免了整数的自动装箱。
再看这个类的属性:

    private int[] mKeys;    private Object[] mValues;    private int mSize;//当前实际存放的对象数量

从源码中我们看到实际上内部是用两个数组在存储的。

put()

在面试中主要会问到put是如何实现的,看一下put的源码,

public void put(int key, E value) {        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);//使用二分查找找到key所在索引        if (i >= 0) {            mValues[i] = value;//i>0说明数组中存在key,可以直接覆盖旧值        } else {            i = ~i;//如果不存在,则将i取反,这与上面ContainerHelpers.binarySearch(mKeys, mSize, key)函数有关,下面会贴出它的源码            if (i < mSize && mValues[i] == DELETED) {                mKeys[i] = key;                mValues[i] = value;                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++;        }    }

从ContainerHelpers.binarySearch(mKeys, mSize, key)这个函数我们可以看出使用了二分查找,而二分查找的前提是数组是有序的,所以mKeys这个数组是有序的。找到key所在的位置后,如果key存在,直接赋值。当不存在key时,先把i取反后得到的值,有两个if语句,第一个if (i < mSize && mValues[i] == DELETED)如果当前要插入的 key 的索引上的值为DELETE并且小于mSize,直接覆盖;第二个if (mGarbage && mSize >= mKeys.length)则是判断是否需要回收数组中应该被删除的值,回收后重新计算i值,因为数组已经发生变化,最终在 i 位置上插入键与值,并且size +1。
我们再来看一下这个二分查找的源码

// 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) {            lo = mid + 1;        } else if (midVal > value) {            hi = mid - 1;        } else {            return mid;  // value found        }    }//lo如果不取反,是最适合插入的位置,但是为了区分没有查找到,所以这里取反,返回一个负数,所以我们看到put()函数里有对i进行是否正负的判断    return ~lo;  // value not present}

那么插入数据是如何插入的呢,mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
,这两行的源码

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

相信这段代码不难懂。

get()

我们再来看下get()是如何实现的。

    /**     * 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];        }    }

通过二分查找找到key的索引将值取出,如果不存在或者被标记删除,则返回valueIfKeyNotFound。

remove()

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

找到该 key 的索引,如果存在,将该索引上的 value 赋值为 DELETED,标记当前状态为待回收mGarbage = true。

gc()

SparseArray删除数据的时候做了优化——使用了延迟整理数组的方法。我们在put中注意到有个gc()调用,remove()中,将这个key赋值为DELETED,key仍然保存在数组中,并没有删除,那么key什么时候被删除呢?gc的作用就是这个。

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

其他几个方法基本一看就能理解。

总结

SparseArray是android提供的一个工具类,其内部实现了一个矩阵压缩算法,大大减少了存储空间,节约内存。此外它的查找算法是二分法,提高了查找的效率。与HashMap相比避免了基本类型数据的自动装箱操作,不需要额外的结构体,单个元素的存储成本更低。

1 0
原创粉丝点击