Android SparseArray 源码解析

原创 2016年08月30日 22:44:44

前段时间内推百度,二面时候被问到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相比避免了基本类型数据的自动装箱操作,不需要额外的结构体,单个元素的存储成本更低。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Android SparseArray源码详解

尊重原创,转载请标明出处    http://blog.csdn.net/abcdef314159 在Android开发中如果使用key为Integer的HashMap,就会出现黄色警告,提示使用Sp...

Android源码分析之SparseArray

首先我们来看看关键字段和ctor: private static final Object DELETED = new Object();    private boolean mGarba...

Android源码分析-SparseArray<E>详解

SparseArray 实现原理在使用HashMap的时候IDE会给出如下提示:SparseArray 类注释如下: SparseArrays map integers to Objects. Un...

SparseArray源码解析

1.SparseArray简介   SparseArray的主要作用是将Integers映射到Objects,相当于Map。当需要将Integers映射到Objects时,SparseArray比Ha...

SparseArray源码解析

SparseArray源码解析Android官方推荐:当使用HashMap(K, V),如果K为整数类型时,使用SparseArray的效率更高.我们通过分析SparseArray的源码,来看一下为什...

【Java源码分析】Android-SparseArray源码分析

类的定义 public class SparseArray implements Cloneable {} 将int映射为对象,比HashMap更节省内存。一方面是避免了对key的自动装箱;另外一个...

【Android应用源码分析】android中HashMap的替代者——SparseArray 源码分析

前言当我们定义HashMap hashMap = new HashMap(); 时IDE会给出一个 警告:用SparseArray来替代,以获取更好性能。HashMap的get和put时间复杂度是O...

SparseArray源码分析

SparseArray源码分析标签(空格分隔): 未分类当里个当,SparseArray 大家都说他的效率比HashMap高,但是具体为什么高,我只是之道他省略了装箱过程,但是里面到底是怎么算的,还是...

【内存优化】SparseArray源码分析

SparseArray是一个Interger和Object的键值对,相当于HashMap它具有以下特点: 1、它不同于Array的是它的键值可以为不连续的数字,这点应该很好理解,数组的索引值是连续固...

SparseArray源码中的G点

通过上一章 小探Android中的SparseArray和HashMap了解了SparseArray和HashMap的一点区别,以及SparseArray的使用场景。这一章主要来记录一下查看Spars...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)