谈谈源码中的SparseArray

谈谈源码中的SparseArray

在Andorid的源码和第三方库中,偶尔能看到该类,咱们先来看一下官方文档的说明以下:数组

SparseArray 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 Int到Object的这种关系。 它设计的目的是为了比HashMap更加节省内存,这是由于:app

  1. 它避免了键值的自动装箱
  2. 他的数据结构不须要依赖额外的对象来完成映射。

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 traditional HashMap, since 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 not significant, less than 50%.less

上面说了,SparseArray经过二分查找键值,这种实现方式不太适合太多的item。一般状况他是比HashMap慢的,可是若是容器只有数百的item,这个性能损失不过重要,不超过50%。性能

实际在Android环境中,咱们的键值也不多有超过上千的,因此SparseArray咱们能在项目中用到的地方仍是很多,若是在Android Stuido出现一个黄色警告叫你替换的话,你就能够考虑替换了。由于对于Android 来讲,内存每每比较重要一点。测试

稀疏数组

下面咱们分析SparseArray的实现方式,从字面意思上翻译过来就是稀疏数组,首先咱们打开源码看一下SparseArray的结构是怎么样的:ui

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;
    
    ........我是省略的代码哟..........   
}

看的出代码中有一个 int[] mKeys 数组, 和一个 Object[] mValues 数组,这两个就是存放键值和对象的地方,mSize是咱们存入了多少键值对。下面咱们将要用一个很是土的办法来看看这个结构是怎么样的,咱们写一段测试代码以下,断点调试。this

1 SparseArray<Object> sparseArray = new SparseArray<>();
2 sparseArray.put(1, "11");
3 sparseArray.put(8, "13");
4 sparseArray.put(4, "12");
5 sparseArray.put(0, "30");

咱们执行第二行代码之后:google

mKey中的结构: {1,0,0,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度 mValues中的结构: {"11"}
mSize: 1spa

咱们执行第三行代码之后:

mKey中的结构: {1,8,0,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度 mValues中的结构: {"11","13"}
mSize: 2

咱们执行第三行代码之后:

mKey中的结构: {1,4,8,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度 mValues中的结构: {"11","12","13"}
mSize: 3

咱们执行第三行代码之后:

mKey中的结构: {0,1,4,8,0,0,0,0,0,0,0,0,0} 后面的0是初始化mKey的长度 mValues中的结构: {"30","11","12","13"}
mSize: 4

从以上的结构中咱们能够判断出,若是咱们想查找一个键值为4的key,那么首先第一步找到key对应在mKey数组中的index,那么对应的value就在对应mValues中的index位置。再仔细观察上面的mKey中的结构,你会发现mKey中的数字是递增的,那么这保证了咱们就能够经过二分查找去找到某一个值在mkey中的位置。ok这个结构分析完毕以后,咱们接下来看看,增,删,查,找功能。

ADD

/**
     * 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 {
            //若是没有找到,这个返回的值就是没有找到的mid+1 那么再取反 正好是当前mKey的存有值      
            //的下一个值,只能说好巧妙~~
            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);
            }

            //GrowingArrayUtils 这个源码看不到,google下源码,就是他会动态增长数组的size,    
            //经过System.arraycopy 实现的,具体本身去google咯
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

Delete

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

Find

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

看了以上的操做,都是经过二分查找来操做的,因此和HashMap相比, 性能仍是有所损失的,最后看看GC的操做

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

总结一下, 就是SpareArray 比HashMap更节约内存,可是性能不如HashMap,在Android系统这内存似金的年代,咱们仍是应该想尽各类办法去节约内存的。 SpareArray还有一个兄弟 叫LongSparsArray 实现原理也是同样的。

相关文章
相关标签/搜索